Skip to content

Commit 5410d61

Browse files
feat(blog): add Angular with pnpm security article (#114)
* feat(blog): add Angular with pnpm security article - Add comprehensive blog post on Angular project setup with pnpm - Document pnpm advantages: storage efficiency, strict node_modules structure, faster installations - Explain Supply-Chain-Security mechanisms: lifecycle script blocking, minimumReleaseAge, onlyBuiltDependencies - Include practical setup guide: Corepack installation, project creation, configuration - Compare npm and pnpm features with detailed comparison tables - Cover security incident case studies and real-world attack vectors - Provide best practices for monorepo workspaces and reproducible builds - Article targets German-speaking Angular developers (published 2026-08-08) * docs(blog): clarify pnpm setup and add migration guide for existing projects - Revise Corepack installation instructions to emphasize updating to latest version - Replace generic placeholder "my-app" with concrete example "book-monkey" throughout - Add explicit `corepack use pnpm@latest` step after project creation to fix packageManager field - Improve narrative flow by replacing impersonal "man/wer" with direct "du" address - Add comprehensive "Bestehende Anwendung auf pnpm umstellen" section with step-by-step migration guide - Include practical migration steps: git branch creation, version locking, CLI configuration, lockfile import, dependency reinstallation, CI/CD updates, and verification - Update package.json example to use placeholder "x.y.z" instead of specific version for clarity - Clarify pnpm approval workflow and build-script handling during migration * makeover * Apply suggestions from code review Co-authored-by: Danny Koppenhagen <mail@k9n.dev> * docs(blog): update Angular with pnpm article header image and attribution - Replace header image from JPG to PNG format (angular-pnpm.jpg → angular-pnpm.png) - Add new cover image file (angular-pnpm.png) - Add attribution note for AI-generated cover image - Update frontmatter header reference to match new image filename * headerbild JPG, deutsch * datum * Apply suggestion from @d-koppenhagen --------- Co-authored-by: Ferdinand Malcher <ferdinand@malcher.media>
1 parent 9623266 commit 5410d61

2 files changed

Lines changed: 370 additions & 0 deletions

File tree

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
---
2+
title: 'Angular mit pnpm: sichere Dependencys und Best Practices'
3+
author: Danny Koppenhagen
4+
mail: mail@k9n.dev
5+
published: 2026-08-13
6+
lastModified: 2026-08-13
7+
keywords:
8+
- Angular
9+
- pnpm
10+
- Corepack
11+
- Supply Chain Security
12+
- Package Manager
13+
- npm
14+
- Security
15+
- Dependencies
16+
- minimumReleaseAge
17+
language: de
18+
header: angular-pnpm.jpg
19+
---
20+
21+
Die Zeiten, in denen der Paketmanager *npm* alternativlos war, sind längst vorbei.
22+
Mit **pnpm** steht ein alternativer Paketmanager zur Verfügung, der effizienter mit Speicherplatz umgeht, schneller installiert und moderne Sicherheitsmechanismen gegen Supply-Chain-Angriffe mitbringt.
23+
24+
In diesem Artikel richten wir ein Angular-Projekt mit pnpm ein, konfigurieren eine sichere Entwicklungsumgebung und schauen uns Best Practices an, die sich in professionellen Teams bewährt haben: von Supply-Chain-Security bis hin zu reproduzierbaren Builds.
25+
26+
## Inhalt
27+
28+
[[toc]]
29+
30+
## Warum überhaupt pnpm?
31+
32+
npm funktioniert, ist überall vorinstalliert und die meisten Tutorials verwenden es.
33+
Warum also wechseln?
34+
Die Antwort liegt in den architektonischen Entscheidungen, die pnpm anders trifft:
35+
36+
**Content-addressable Store:**
37+
Jede Paketversion wird genau einmal in einem zentralen Store gespeichert.
38+
Mehrere Projekte referenzieren dasselbe Paket per Hardlink, was erheblich Speicherplatz spart.
39+
40+
**Strikte `node_modules`-Struktur:**
41+
pnpm erstellt keine flache `node_modules`-Struktur, sondern nutzt Symlinks, bei denen jedes Paket nur auf seine deklarierten Abhängigkeiten zugreifen kann.
42+
Das verhindert das *Phantom-Dependency*-Problem: Code kann nicht versehentlich auf transitive Abhängigkeiten zugreifen.
43+
44+
**Schnellere Installationen:**
45+
Durch den zentralen Store und Hardlinks sind Installationen deutlich schneller, insbesondere bei wiederholten Installationen und in CI-Pipelines.
46+
47+
**Monorepo-Unterstützung:**
48+
pnpm bietet erstklassige [Workspace-Unterstützung](https://pnpm.io/workspaces) mit Catalogs (zentrale Versionsverwaltung), dem Workspace-Protokoll und effizienter Verwaltung mehrerer Pakete.
49+
50+
**Security by Default:**
51+
Seit Version 10 blockiert pnpm standardmäßig Lifecycle-Scripts in Dependencys.
52+
Damit wird eine ganze Kategorie von Supply-Chain-Angriffen von Haus aus verhindert.
53+
54+
| Merkmal | npm | pnpm |
55+
|---------|-----|------|
56+
| Speicherverbrauch | hoch (jedes Projekt eigene Kopie) | niedrig (Content-addressable Store) |
57+
| Installationsgeschwindigkeit | mittel | schnell |
58+
| Phantom Dependencies | möglich (flache Struktur) | ausgeschlossen (strikte Struktur) |
59+
| Lifecycle Scripts | werden ausgeführt | standardmäßig blockiert (ab v10) |
60+
| Monorepo-Support | Workspaces (basic) | Workspaces + Catalogs |
61+
| minimumReleaseAge | ab npm 11.10 (`min-release-age`) | ab pnpm 10.16 |
62+
63+
## Neues Angular-Projekt mit pnpm erstellen
64+
65+
### pnpm installieren und Version fixieren
66+
67+
Für die Schnittstelle zwischen npm und alternativen Paketmanagern wird das Modul `corepack` verwendet.
68+
Es wird seit Node.js 16.13 mitgeliefert.
69+
Der folgende Befehl aktualisiert Corepack auf die aktuelle Version. Das ist insbesondere wegen veralteter Signaturen in älteren Corepack-Versionen sinnvoll. Falls Corepack in deiner Node.js-Installation bereits aktuell ist, kannst du diesen Schritt überspringen. Anschließend aktivierst du pnpm:
70+
71+
```bash
72+
npm install --global corepack@latest
73+
corepack enable pnpm
74+
```
75+
76+
### Projekt erzeugen
77+
78+
Beim Erstellen eines neuen Projekts muss der Package Manager explizit angegeben werden.
79+
Ein einfaches `pnpm dlx @angular/cli@latest new book-monkey` reicht **nicht**: Die CLI würde trotzdem npm verwenden, da sie den aufrufenden Package Manager nicht automatisch erkennt.
80+
Stattdessen musst du der Angular CLI explizit mitteilen, welcher Paketmanager eingesetzt werden soll.
81+
82+
```bash
83+
# Bei globaler Installation:
84+
ng new book-monkey --package-manager pnpm
85+
86+
# Direkt ohne global installierte Angular CLI via npx:
87+
npx @angular/cli@latest new book-monkey --package-manager pnpm
88+
89+
# Direkt mit pnpm dlx:
90+
pnpm dlx @angular/cli@latest new book-monkey --package-manager pnpm
91+
```
92+
93+
Nach der Erstellung wechselst du in den Projektordner und fixierst dort die verwendete Version:
94+
95+
```bash
96+
cd book-monkey
97+
corepack use pnpm@latest
98+
```
99+
100+
Der Befehl ergänzt das Feld `packageManager` in der `package.json`. Corepack aktiviert dann bei allen Teammitgliedern automatisch dieselbe pnpm-Version.
101+
102+
Das Flag `--package-manager pnpm` bewirkt zwei Dinge:
103+
104+
1. Die Pakete werden mit pnpm installiert.
105+
2. In der `angular.json` wird `"cli": { "packageManager": "pnpm" }` eingetragen.
106+
107+
Dieser Eintrag teilt der Angular CLI mit, welchen Package Manager sie für alle zukünftigen Operationen (`ng add`, `ng update`, etc.) verwenden soll.
108+
Die CLI delegiert dann alle Paketoperationen transparent an pnpm, z. B. `ng add @angular/cdk`, `ng update`, `ng generate` funktionieren unverändert.
109+
110+
> **Tipp:** Als zusätzliche Absicherung gegen versehentliches `npm install` empfiehlt sich ein `preinstall`-Script:
111+
>
112+
> ```json
113+
> "scripts": {
114+
> "preinstall": "npx only-allow pnpm"
115+
> }
116+
> ```
117+
118+
## Projektstruktur und `node_modules`
119+
120+
Wenn du zum ersten Mal ein pnpm-Projekt öffnest, stellst du fest, dass `node_modules` anders aufgebaut ist:
121+
122+
```
123+
node_modules/
124+
├── .pnpm/ ← Virtueller Store mit allen Paketen
125+
├── @angular/core ← Symlink nach .pnpm/...
126+
├── rxjs ← Symlink nach .pnpm/...
127+
└── ...
128+
```
129+
130+
Jedes Paket in der obersten Ebene ist ein Symlink in den Ordner `.pnpm`, der wiederum Hardlinks zum globalen Store enthält.
131+
Die IDE-Unterstützung (TypeScript-Auflösung, Autocomplete) sowie Build-Tools (Vite, esbuild) funktionieren damit problemlos.
132+
133+
## Supply-Chain-Security mit pnpm
134+
135+
Dies ist der wichtigste Grund, warum pnpm heute nicht nur eine Performance-Optimierung ist, sondern eine bewusste Architekturentscheidung für die Sicherheit eines Projekts.
136+
137+
Supply-Chain-Attacken im npm-Ökosystem haben sich in den letzten Jahren dramatisch gehäuft.
138+
Einige bekannte Vorfälle:
139+
140+
| Vorfall | Jahr | Auswirkung |
141+
|---------|------|------------|
142+
| ua-parser-js | 2021 | Kryptominer in 8-Mio-Downloads-Paket, 4 Stunden online |
143+
| colors & faker | 2022 | Maintainer sabotierte eigene Pakete |
144+
| node-ipc | 2022 | Politisch motivierte Datei-Überschreibung |
145+
| eslint-config-prettier | 2025 | Phishing → gestohlener npm-Token → Malware via postinstall |
146+
| @ctrl/tinycolor (Shai-Hulud) | 2025 | Erster wurmartiger Angriff im npm-Ökosystem, 100+ Pakete betroffen |
147+
148+
Alle diese Angriffe nutzen denselben Grundmechanismus: Ein vertrauenswürdiges Paket wird kompromittiert, und bei der nächsten Installation wird Schadcode über `postinstall`-Scripts ausgeführt.
149+
pnpm adressiert diese Risiken mit konkreten, konfigurierbaren Mechanismen.
150+
151+
### pnpm-Features gegen Supply-Chain-Risiken
152+
153+
| Risiko | pnpm-Feature | Wirkung |
154+
|--------|--------------|---------|
155+
| Schadcode via `postinstall` | Lifecycle Scripts blockiert | Kein automatischer Code bei `pnpm install` |
156+
| Kompromittiertes Release | `minimumReleaseAge` | Neue Versionen erst nach Wartezeit installierbar |
157+
| Unbekannte Build Scripts | `onlyBuiltDependencies` | Nur explizit erlaubte Pakete dürfen Scripts ausführen |
158+
| Git-Dependencys mit Schadcode | `blockExoticSubdeps` | Git-hosted Deps können keine `prepare`-Scripts ausführen |
159+
| Nicht-reproduzierbare Builds | `pnpm-lock.yaml` + `--frozen-lockfile` | Exakt gleiche Installation in jedem Environment |
160+
161+
### Lifecycle Scripts kontrollieren
162+
163+
Seit pnpm 10 werden Scripts vom Typ `preinstall`, `install` und `postinstall` **nicht mehr automatisch ausgeführt**.
164+
Das ist der bedeutendste Sicherheitsunterschied zu npm.
165+
166+
Bei einem frischen Angular-Projekt sieht man nach `pnpm install` eine Meldung wie:
167+
168+
```
169+
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: @parcel/watcher@2.6.0, esbuild@0.28.1, lmdb@3.5.6, msgpackr-extract@3.0.4
170+
Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.
171+
```
172+
173+
Das ist kein Fehler: pnpm teilt mit, dass einige Pakete Build-Scripts mitbringen, die nicht ausgeführt wurden.
174+
Ohne diese Scripts fehlen ggf. native Binarys (z. B. für `esbuild`).
175+
176+
Die Lösung: bewusste Freigabe mit `pnpm approve-builds`.
177+
Dieser Befehl zeigt interaktiv alle Pakete mit Build-Scripts an.
178+
Genehmigte Pakete werden in der Datei `pnpm-workspace.yaml` unter `onlyBuiltDependencies` gespeichert:
179+
180+
```yaml
181+
onlyBuiltDependencies:
182+
- '@parcel/watcher'
183+
- esbuild
184+
- lmdb
185+
- msgpackr-extract
186+
187+
ignoredBuiltDependencies:
188+
- puppeteer
189+
```
190+
191+
Wird später ein neues Paket mit Build-Scripts hinzugefügt, erscheint die Meldung erneut, und du entscheidest bewusst über die Freigabe.
192+
193+
> **Hinweis beim Wechsel von npm:** Wenn du bisher `ignore-scripts=true` in der `.npmrc` verwendet hast, solltest du diese Einstellung bei pnpm 10+ **entfernen**.
194+
> pnpm blockiert Lifecycle Scripts von Dependencys bereits standardmäßig, auch ohne `.npmrc`-Eintrag.
195+
> Ein zusätzliches `ignore-scripts=true` würde auch die über `onlyBuiltDependencies` explizit freigegebenen Scripts blockieren und damit den granularen Freigabe-Mechanismus aushebeln.
196+
> Native Binarys (z. B. für esbuild) könnten dann trotz Freigabe nicht gebaut werden.
197+
198+
### `minimumReleaseAge`: Quarantäne für neue Versionen
199+
200+
Die meisten kompromittierten Pakete werden innerhalb weniger Stunden erkannt und entfernt.
201+
`minimumReleaseAge` nutzt dieses Zeitfenster als Schutz:
202+
203+
```yaml
204+
minimumReleaseAge: 1440
205+
```
206+
207+
Der Wert `1440` entspricht 24 Stunden (in Minuten).
208+
pnpm installiert keine Paketversion, die weniger als 24 Stunden alt ist, weder direkt noch transitiv.
209+
210+
Zur Einordnung: Der [Angriff auf eslint-config-prettier](https://socket.dev/blog/eslint-prettier-malware) (2025) war nach 6 Stunden bereinigt, der [Shai-Hulud-Wurm](https://socket.dev/blog/shai-hulud-npm-worm) (2025) nach 12 Stunden, die [Kompromittierung von ua-parser-js](https://github.com/nicedoc/nicedoc/issues/1) (2021) nach 4 Stunden.
211+
Mit einer 24-Stunden-Quarantäne wären alle diese Angriffe ins Leere gelaufen.
212+
213+
> **Hinweis:** `minimumReleaseAge` schützt nicht grundsätzlich vor allen Supply-Chain-Angriffen, reduziert aber effektiv das Risiko kurzfristig kompromittierter Releases. Das ist die mit Abstand häufigste Angriffsform.
214+
215+
Seit pnpm 10.19 lässt sich mit `minimumReleaseAgeExclude` die Wartezeit für bestimmte Pakete (z. B. interne) deaktivieren:
216+
217+
```yaml
218+
minimumReleaseAge: 1440
219+
minimumReleaseAgeExclude:
220+
- '@my-company/*'
221+
```
222+
223+
### blockExoticSubdeps: Git-Dependencys absichern
224+
225+
Seit pnpm 10.26 werden Dependencys von Git-Repositorys daran gehindert, `prepare`-Scripts auszuführen.
226+
Ausnahmen lassen sich verwalten, indem die Pakete explizit in `onlyBuiltDependencies` gelistet werden:
227+
228+
```yaml
229+
blockExoticSubdeps: true
230+
```
231+
232+
### Lockfile und reproduzierbare Builds
233+
234+
Die `pnpm-lock.yaml` ist kein optionales Artefakt, sondern eine Sicherheitsfunktion.
235+
Sie stellt sicher, dass überall exakt dieselben Paketversionen installiert werden.
236+
237+
**Goldene Regeln:**
238+
239+
1. **Niemals löschen**: Das Lockfile gehört ins Repository.
240+
2. **`--frozen-lockfile` in CI**: verhindert Lockfile-Aktualisierungen bei der Installation
241+
3. **Reviewen bei PRs**: Änderungen am Lockfile sollten bewusst geprüft werden.
242+
243+
```bash
244+
# In CI-Pipelines immer verwenden:
245+
pnpm install --frozen-lockfile
246+
```
247+
248+
Dieses Kommando ist das Äquivalent zu `npm ci` mit `package-lock.json`. Durch die Hardlink-basierte Installation ist pnpm aber auch in CI-Pipelines deutlich schneller.
249+
250+
## Empfohlene Konfiguration
251+
252+
Die folgende Konfiguration fasst alle besprochenen Best Practices zusammen.
253+
Wichtig: `.npmrc` und `pnpm-workspace.yaml` gehören versioniert ins Repository, damit die Security Policies automatisch für alle Teammitglieder und CI-Pipelines gelten.
254+
255+
### `.npmrc`
256+
257+
```ini
258+
# Strikte Peer Dependencies: Konflikte werden als Fehler behandelt
259+
strict-peer-dependencies=true
260+
261+
# Keine automatische Installation von Peer Dependencies
262+
auto-install-peers=false
263+
264+
# Integritätsprüfung des Stores
265+
verify-store-integrity=true
266+
267+
# Höchste verfügbare Version bei der Auflösung bevorzugen
268+
resolution-mode=highest
269+
```
270+
271+
### `pnpm-workspace.yaml`
272+
273+
```yaml
274+
packages: []
275+
276+
onlyBuiltDependencies:
277+
- '@parcel/watcher'
278+
- esbuild
279+
- lmdb
280+
- msgpackr-extract
281+
282+
minimumReleaseAge: 1440
283+
```
284+
285+
### `package.json` (Auszug)
286+
287+
```json
288+
{
289+
"name": "book-manager",
290+
"version": "0.0.0",
291+
"packageManager": "pnpm@x.y.z",
292+
"scripts": {
293+
"preinstall": "npx only-allow pnpm",
294+
"start": "ng serve",
295+
"build": "ng build",
296+
"test": "ng test"
297+
},
298+
"dependencies": {
299+
"@angular/core": "^22.0.0",
300+
"@angular/common": "^22.0.0",
301+
"@angular/compiler": "^22.0.0",
302+
"@angular/platform-browser": "^22.0.0",
303+
"@angular/platform-browser-dynamic": "^22.0.0",
304+
"@angular/router": "^22.0.0",
305+
"@angular/forms": "^22.0.0",
306+
"rxjs": "^7.8.0",
307+
"zone.js": "~0.15.0"
308+
},
309+
"devDependencies": {
310+
"@angular/compiler-cli": "^22.0.0",
311+
"typescript": "~5.8.0"
312+
}
313+
}
314+
```
315+
316+
## Bestehende Anwendung auf pnpm umstellen
317+
318+
Wenn du bereits eine Angular-Anwendung wie betreibst, brauchst du kein neues Projekt zu erzeugen. Die Unterschiede zum neuen Setup beschränken sich auf die Übernahme der bestehenden Konfiguration und Lockfile:
319+
320+
1. **Arbeitsstand sichern:** Lege einen Git-Branch an und prüfe, dass die Anwendung vor der Migration funktioniert.
321+
2. **pnpm-Version festlegen:** Führe im Projektverzeichnis `corepack use pnpm@latest` aus. Corepack ergänzt das `packageManager`-Feld in der vorhandenen `package.json` und trägt die konkret aufgelöste Version ein.
322+
3. **Angular CLI konfigurieren:** Ergänze in der bestehenden `angular.json` unter `cli` die Eigenschaft `"packageManager": "pnpm"`. Andere Projekteinstellungen und der verwendete Builder bleiben unverändert:
323+
324+
```json
325+
{
326+
"cli": {
327+
"packageManager": "pnpm"
328+
}
329+
}
330+
```
331+
332+
4. **Lockfile übernehmen:** Liegt eine `package-lock.json` oder `yarn.lock` vor, führe zunächst `pnpm import` aus. Dadurch entsteht die `pnpm-lock.yaml`. Lösche die alte Lockfile erst, nachdem du die neue geprüft hast.
333+
5. **Abhängigkeiten neu installieren:** Entferne `node_modules` und installiere anschließend mit `pnpm install`:
334+
335+
```bash
336+
rm -rf node_modules
337+
pnpm install
338+
```
339+
340+
6. **Automatisierung anpassen:** Ersetze in CI und lokalen Skripten `npm install` beziehungsweise `npm ci` durch `pnpm install` beziehungsweise `pnpm install --frozen-lockfile`. Prüfe außerdem direkte Aufrufe von `npx` und `npm`.
341+
7. **Migration prüfen:** Führe Build, Tests und Entwicklungsserver aus. Bei blockierten Build-Scripts entscheidest du mit `pnpm approve-builds`, welche Abhängigkeiten diese ausführen dürfen.
342+
343+
Die ausführliche pnpm-Konfiguration aus den vorherigen Abschnitten – etwa `.npmrc`, `pnpm-workspace.yaml` und `onlyBuiltDependencies` – kannst du anschließend schrittweise übernehmen.
344+
345+
## Fazit
346+
347+
pnpm bietet mehr als einen schnellen Package Manager, sondern liefert Werkzeuge, um Dependency Management bewusst zu gestalten:
348+
349+
- **Security-Features** wie `minimumReleaseAge`, kontrollierte Build-Scripts und `blockExoticSubdeps` reduzieren Risiken in der Software Supply Chain.
350+
- **Strikte `node_modules`-Isolation** verhindert Phantom Dependencies.
351+
- **Reproduzierbare Installationen** via Lockfile und `--frozen-lockfile` sichern konsistente Builds.
352+
353+
Dabei bleibt der gewohnte Angular-Workflow vollständig erhalten, denn die CLI delegiert Installationen transparent an pnpm.
354+
Wenn du später mehrere Anwendungen oder Bibliotheken verwalten möchtest, kannst du dieses Fundament ohne Architekturbruch um Workspaces, Catalogs und Nx erweitern.
355+
356+
## Weiterführende Links
357+
358+
- [pnpm Dokumentation](https://pnpm.io)
359+
- [pnpm Supply-Chain-Security](https://pnpm.io/supply-chain-security)
360+
- [pnpm Catalogs](https://pnpm.io/catalogs)
361+
- [Nx mit pnpm](https://nx.dev/concepts/integrated-vs-package-based#package-based)
362+
- [Angular CLI – Package Manager Konfiguration](https://angular.dev/tools/cli/setup-local)
363+
- [Corepack Dokumentation](https://nodejs.org/api/corepack.html)
364+
- [Socket.dev – Security-Analysen zu npm-Angriffen](https://socket.dev/blog)
365+
366+
<small>**Titelbild:** generiert mit AI, bearbeitet</small>
367+
368+
<hr>
369+
370+
<small>Vielen Dank an Ferdinand Malcher für das Review und das wertvolle Feedback!</small>
123 KB
Loading

0 commit comments

Comments
 (0)