Technical Briefing: Carbon Manufacturing ERP/MES/QMS
The Problem
Manufacturers face fragmented tooling with limited modern API integration, vendor lock-in, and inflexible ERP/MES solutions that don't adapt to unique workflows. Carbon addresses these gaps with an open-core system combining ERP, MES, and QMS capabilities in a single extensible platform.
What This Does
Carbon provides a full-stack manufacturing platform with:
- ERP Core (
apps/erp/): - Sales, purchasing, inventory, accounting (GL) - Material requirements planning (MRP) - Traceability (lot/serial tracking) - Nested bills of material (BoM)
- MES Capabilities (
apps/assembler/): - Shop floor execution - Job operations tracking - Assembly planning (Rust-based apps/assembler/src/main.rs)
- QMS Features (
packages/quality/): - Inspections - Non-conformance tracking - CAPA management
The system is built on a monorepo structure with:
- TypeScript/React frontend (
apps/academy/, apps/erp/) - Rust backend components (
apps/assembler/, crates/) - Supabase database integration
- Docker containerization
How It Is Wired
The system follows a clean architectural separation:
- API Entry Points:
- ERP:
apps/erp/package.json exports API handlers - Assembly:
apps/assembler/src/main.rs provides Rust-based services - Data Flow: 1. Frontend components (
apps/erp/app/components/) make API calls 2. Requests route through Supabase RLS policies for authorization 3. Core business logic executes in Rust services 4. Database operations handled via Supabase migrations (packages/database/)
Critical dependencies:
packages/database/ manages all schema migrationspackages/core/ contains shared business logiccrates/geometry/ handles spatial calculations for assembly operations
How To Use It
Setup:
git clone https://github.com/moses-y/carbon
cd carbon
pnpm install
docker-compose up -d
Configuration:
- Environment variables in
.env (see .env.example) - Database configuration in
packages/database/
Running:
- Start services:
pnpm run dev - Access ERP at
http://localhost:3000 - Assembly services run via
apps/assembler/ Rust component
Real-World Use
A typical workflow might involve:
- Creating a new product configuration via the configurator
- Generating a BoM in the ERP system
- Scheduling assembly jobs through the MES interface
- Executing quality inspections via QMS tools
- Tracking inventory movements with full traceability
Code Health & Issues
- Measured Analysis:
- 636 test files identified
- 426 documentation files present
- Low severity: Secret-shaped paths detected in:
contrib/building/examples/quote-configurator/.envpackages/database/supabase/migrations/20260817123719_integration-jsonschema-drop-secrets.sql
Observations:
- High test coverage evident from test file count
- Comprehensive documentation present
- Monorepo structure may introduce complexity for new contributors
The Bottom Line
Carbon provides a robust, modern manufacturing platform with strong API-first design and extensibility. The monorepo structure and mixed-language architecture (TypeScript/Rust) deliver performance while maintaining developer productivity. The project demonstrates solid engineering practices with comprehensive testing and documentation. Best suited for manufacturers needing an integrated ERP/MES/QMS solution with flexibility for customization.
What would stop me shipping this
Ranked by severity × confidence × production reach. Reach is the honest discriminator across a collection that is mostly other people's code: the same finding matters more in something that ships.
critical1
high3
medium6
low1
Rotate the credentials in the committed environment filecritical
contrib/building/examples/quote-configurator/.env
CARBON_PUBLIC_KEY hold generated values
These values are loaded by the application at boot, so a generated value here is a working credential for something that runs.
Fix: Rotate each named credential, remove the file from the index, and replace it with a keys-only example.
Pin third-party GitHub Actions to a commit SHAhigh7 occurrences
.github/workflows
aws-actions/amazon-ecr-login@v2, docker/setup-buildx-action@v3, docker/build-push-action@v5, aquasecurity/trivy-action@master
A tag can be moved, so the action running with your token and secrets is whatever its owner last pushed; this is how tj-actions/changed-files leaked secrets from thousands of repos.
Fix: Replace each @vN with the 40-character commit SHA, keep # vN as a comment, and let Dependabot bump the SHAs.
Upgrade the pinned dependency carrying a published advisoryhigh10 occurrences
tokio@1 CVE-2021-45710 (high) +7 more; futures-util@0.3 CVE-2020-35908 (medium) +1 more
The pinned version is the one that installs, so a published advisory against it describes this deployment rather than a hypothetical one, and the advisory is also a public description of how to exploit it.
Fix: Upgrade to the fixed version named in the advisory, then commit the lockfile.
Remove the committed .env and rotate what it holdshigh2 occurrences
contrib/building/examples/quote-configurator/.env
contrib/building/examples/quote-configurator/.env, contrib/building/examples/upload-3d-model/.env
A tracked .env is the most common route for a working key to reach a public clone, and it is the file the app actually loads, so the value is usually live.
Fix: git rm --cached the file, add it to .gitignore, rotate every credential it names, commit a .env.example with empty values.
Declare least-privilege permissions for GITHUB_TOKENmedium4 occurrences
.github/workflows/assembler.yml
4 workflow(s) declare no permissions, 3 of them reference secrets
With no declaration the token inherits the repository default, so any injected step can push commits or mint releases from inside your own CI.
Fix: Add permissions: contents: read at the top of the workflow and widen per job only where needed.
Pin the container base image by digestmedium3 occurrences
Dockerfile
node:22-slim, node:22-slim, node:22-slim
An untagged or mutable base means today's build and last month's contain different libc and a different CVE set, with no record of which shipped.
Fix: Use image:tag@sha256:<digest> and enable Dependabot's docker ecosystem.
Add a pre-commit secret gatemedium
a secret-shaped file is tracked and no repo-level gate is visible
Without a gate the same class of leak recurs on the next commit, so a leak finding is the symptom and this is the cause.
Fix: Add a pre-commit hook scanning staged content, and enable push protection.
Move large binaries to Git LFS or out of the repositorymedium
packages/database/src/swagger-docs-schema.ts
1 blobs over 5MB: swagger-docs-schema.ts 5.0MB
One repository here carries twenty blobs over 5MB including a 10.9MB spreadsheet, so every clone and every CI checkout pays for data nobody diffs.
Fix: Track those extensions with LFS, or move datasets to object storage and fetch them in a setup step.
Set persist-credentials: false on checkoutmedium
.github/workflows/assembler.yml
checkout keeps the token, then dependencies are installed
The token stays in .git/config for every later step, so a malicious postinstall script reads a pushable credential without one ever being passed to it.
Fix: Add with: persist-credentials: false, and pass an explicit token only to the step that pushes.
Add a non-root USER to the imagemedium
Dockerfile
CMD or ENTRYPOINT with no USER directive
A process running as root in the container is root against every mounted volume, and it turns any container escape or writable-mount mistake from a contained problem into a host one.
Fix: Create an unprivileged user, chown what it needs, and end the Dockerfile with USER.
Set timeout-minutes on the workflow jobslow4 occurrences
.github/workflows/assembler.yml
4 workflow(s) declare no job timeout
A wedged step runs to the six-hour platform default, which on a two-hourly schedule means three runs overlap behind it.
Fix: Add timeout-minutes with a realistic bound to each job.
Checked deterministically against the repository tree and a bounded set of its files: committed credentials, unpinned actions and base images, missing lockfiles and update bots, workflows that discard failures, published advisories against the declared dependencies, runtime configuration, licensing and notebook reproducibility. No language model is involved in this section.