@rune-kit/rune 2.3.3 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +51 -1
  2. package/compiler/__tests__/scripts-bundling.test.js +284 -0
  3. package/compiler/__tests__/tier-override.test.js +41 -0
  4. package/compiler/adapters/antigravity.js +4 -0
  5. package/compiler/adapters/codex.js +4 -0
  6. package/compiler/adapters/cursor.js +4 -0
  7. package/compiler/adapters/generic.js +4 -0
  8. package/compiler/adapters/openclaw.js +4 -0
  9. package/compiler/adapters/opencode.js +4 -0
  10. package/compiler/adapters/windsurf.js +4 -0
  11. package/compiler/emitter.js +85 -5
  12. package/compiler/transforms/scripts-path.js +18 -0
  13. package/extensions/zalo/PACK.md +20 -1
  14. package/extensions/zalo/references/conversation-management.md +214 -0
  15. package/extensions/zalo/references/eval-scenarios.md +157 -0
  16. package/extensions/zalo/references/listen-mode.md +237 -0
  17. package/extensions/zalo/references/mcp-production.md +274 -0
  18. package/extensions/zalo/references/multi-account-proxy.md +224 -0
  19. package/extensions/zalo/references/vietqr-banking.md +160 -0
  20. package/package.json +2 -3
  21. package/skills/marketing/SKILL.md +3 -0
  22. package/skills/sentinel/SKILL.md +4 -1
  23. package/skills/sentinel/references/auth-crypto-reference.md +192 -0
  24. package/skills/sentinel/references/desktop-security.md +201 -0
  25. package/skills/sentinel/references/supply-chain.md +160 -0
  26. package/skills/slides/SKILL.md +142 -0
  27. package/skills/slides/scripts/build-deck.js +158 -0
  28. package/docs/ANTIGRAVITY-GAP-ANALYSIS.md +0 -369
  29. package/docs/ARCHITECTURE.md +0 -332
  30. package/docs/COMMUNITY-PACKS.md +0 -109
  31. package/docs/CONTRIBUTING-L4.md +0 -215
  32. package/docs/CROSS-IDE-ANALYSIS.md +0 -164
  33. package/docs/EXTENSION-TEMPLATE.md +0 -126
  34. package/docs/MESH-RULES.md +0 -34
  35. package/docs/MULTI-PLATFORM.md +0 -804
  36. package/docs/SKILL-DEPTH-AUDIT.md +0 -191
  37. package/docs/SKILL-TEMPLATE.md +0 -118
  38. package/docs/TRADE-MATRIX.md +0 -327
  39. package/docs/VERSIONING.md +0 -91
  40. package/docs/VISION.md +0 -263
  41. package/docs/assets/demo-subtitles.srt +0 -215
  42. package/docs/assets/end-card.html +0 -276
  43. package/docs/assets/mesh-diagram.html +0 -654
  44. package/docs/assets/thumbnail.html +0 -295
  45. package/docs/guides/cli.md +0 -403
  46. package/docs/guides/index.html +0 -1450
  47. package/docs/index.html +0 -1005
  48. package/docs/references/claudekit-analysis.md +0 -414
  49. package/docs/references/voltagent-analysis.md +0 -189
  50. package/docs/script.js +0 -495
  51. package/docs/skills/index.html +0 -832
  52. package/docs/style.css +0 -958
  53. package/docs/video-demo-plan.md +0 -172
@@ -3,7 +3,7 @@ name: sentinel
3
3
  description: Automated security gatekeeper. Blocks unsafe code before commit — secret scanning, OWASP top 10, dependency audit, permission checks. A GATE, not a suggestion.
4
4
  metadata:
5
5
  author: runedev
6
- version: "0.7.0"
6
+ version: "0.8.0"
7
7
  layer: L2
8
8
  model: sonnet
9
9
  group: quality
@@ -96,6 +96,7 @@ Use `Grep` on all changed files for core patterns: `sk-`, `AKIA`, `ghp_`, `ghs_`
96
96
  Any match = **BLOCK**. Do not proceed to later steps if BLOCK findings exist — report immediately.
97
97
 
98
98
  ### Step 2 — Dependency Audit
99
+ <MUST-READ path="references/supply-chain.md" trigger="When dependency changes detected (package.json, package-lock.json, requirements.txt, Cargo.toml modified) — load typosquatting prevention, lock file rules, SRI, npm hardening"/>
99
100
 
100
101
  Use `Bash` to run the appropriate audit command for the detected package manager:
101
102
  - npm/pnpm/yarn: `npm audit --json` (parse JSON, extract critical + high severity)
@@ -122,6 +123,7 @@ If 3+ signals fire for a single dependency → **BLOCK** with recommendation: "C
122
123
 
123
124
  ### Step 3 — OWASP Check
124
125
  <MUST-READ path="references/owasp-patterns.md" trigger="Before scanning for OWASP issues — load code examples and detection signals for SQL injection, XSS, CSRF, input validation"/>
126
+ <MUST-READ path="references/auth-crypto-reference.md" trigger="When authentication, password hashing, encryption, or token management patterns detected — load Argon2id params, JWT best practices, OAuth2 PKCE, AES-256-GCM, fail-closed principle"/>
125
127
 
126
128
  Scan changed files for SQL injection (string concat/interpolation in SQL) → **BLOCK**, XSS (`innerHTML`, `dangerouslySetInnerHTML` without sanitization) → **BLOCK**, CSRF (forms without token, cookies without SameSite) → **WARN**, and missing input validation (raw `req.body` → DB) → **WARN**. Load reference for code examples and precise detection signals.
127
129
 
@@ -140,6 +142,7 @@ When invoked on `SKILL.md`, `extensions/*/PACK.md`, `.rune/*.md`, or agent files
140
142
 
141
143
  ### Step 4.5 — Framework-Specific Security Patterns
142
144
  <MUST-READ path="references/framework-patterns.md" trigger="When framework files are detected in the changed set — load patterns for the specific framework(s) found"/>
145
+ <MUST-READ path="references/desktop-security.md" trigger="When Electron or Tauri project detected (package.json contains electron, @tauri-apps/cli, or tauri.conf.json exists) — load BrowserWindow config, IPC validation, scope restrictions, code signing"/>
143
146
 
144
147
  Apply only when the framework is detected in changed files. Covers Django (DEBUG=True, missing permissions, CSRF removal), React/Next.js (localStorage JWT, dangerouslySetInnerHTML), Node.js/Express/Fastify (wildcard CORS, missing helmet), Python (pickle.loads, yaml.load unsafe). Load reference for the complete check table per framework.
145
148
 
@@ -0,0 +1,192 @@
1
+ # Auth & Cryptography Reference
2
+
3
+ > Loaded by `sentinel` when authentication, password hashing, encryption, or token management patterns detected.
4
+
5
+ ---
6
+
7
+ ## Password Hashing
8
+
9
+ ### Recommended: Argon2id
10
+
11
+ ```javascript
12
+ import { hash, verify } from '@node-rs/argon2';
13
+
14
+ // Hash — use these parameters minimum
15
+ const hashed = await hash(password, {
16
+ memoryCost: 65536, // 64 MB
17
+ timeCost: 3, // 3 iterations
18
+ parallelism: 4, // 4 threads
19
+ outputLen: 32, // 32 bytes
20
+ });
21
+
22
+ // Verify
23
+ const valid = await verify(hashed, password);
24
+ ```
25
+
26
+ ### Acceptable: bcrypt
27
+
28
+ ```javascript
29
+ import bcrypt from 'bcrypt';
30
+
31
+ const ROUNDS = 12; // minimum 12, never below 10
32
+ const hashed = await bcrypt.hash(password, ROUNDS);
33
+ const valid = await bcrypt.compare(password, hashed);
34
+ ```
35
+
36
+ ### Severity Table
37
+
38
+ | Algorithm | Verdict | Action |
39
+ |-----------|---------|--------|
40
+ | Argon2id | PASS | Preferred |
41
+ | bcrypt (rounds ≥ 12) | PASS | Acceptable |
42
+ | bcrypt (rounds < 10) | WARN | Increase rounds |
43
+ | scrypt | WARN | Prefer Argon2id |
44
+ | SHA-256/SHA-512 + salt | BLOCK | Not a password hash |
45
+ | MD5 / SHA-1 | BLOCK | Broken, must replace |
46
+ | Plaintext | BLOCK | Critical vulnerability |
47
+
48
+ ---
49
+
50
+ ## JWT Best Practices
51
+
52
+ ### Token Lifecycle
53
+
54
+ ```javascript
55
+ // Access token: short-lived (15 min)
56
+ const accessToken = jwt.sign(
57
+ { sub: user.id, role: user.role },
58
+ process.env.JWT_SECRET,
59
+ { expiresIn: '15m', algorithm: 'HS256' }
60
+ );
61
+
62
+ // Refresh token: longer (7 days), stored in HttpOnly cookie
63
+ const refreshToken = jwt.sign(
64
+ { sub: user.id, type: 'refresh' },
65
+ process.env.JWT_REFRESH_SECRET,
66
+ { expiresIn: '7d' }
67
+ );
68
+
69
+ res.cookie('refresh_token', refreshToken, {
70
+ httpOnly: true, // No JavaScript access
71
+ secure: true, // HTTPS only
72
+ sameSite: 'strict', // No cross-site
73
+ maxAge: 7 * 24 * 60 * 60 * 1000,
74
+ path: '/api/auth/refresh', // Only sent to refresh endpoint
75
+ });
76
+ ```
77
+
78
+ ### JWT BLOCK Patterns
79
+
80
+ | Pattern | Severity | Fix |
81
+ |---------|----------|-----|
82
+ | `algorithm: 'none'` | BLOCK | Always specify algorithm |
83
+ | No `expiresIn` | BLOCK | Tokens MUST expire |
84
+ | Secret < 32 chars | WARN | Use 256-bit minimum |
85
+ | JWT in localStorage | WARN | Use HttpOnly cookie for refresh |
86
+ | No audience/issuer validation | WARN | Add `aud` and `iss` claims |
87
+
88
+ ---
89
+
90
+ ## OAuth2 with PKCE
91
+
92
+ ```javascript
93
+ // 1. Generate PKCE challenge
94
+ const codeVerifier = crypto.randomBytes(32).toString('base64url');
95
+ const codeChallenge = crypto
96
+ .createHash('sha256')
97
+ .update(codeVerifier)
98
+ .digest('base64url');
99
+
100
+ // 2. Authorization URL
101
+ const authUrl = new URL('https://provider.com/authorize');
102
+ authUrl.searchParams.set('response_type', 'code');
103
+ authUrl.searchParams.set('client_id', CLIENT_ID);
104
+ authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
105
+ authUrl.searchParams.set('code_challenge', codeChallenge);
106
+ authUrl.searchParams.set('code_challenge_method', 'S256');
107
+ authUrl.searchParams.set('state', crypto.randomBytes(16).toString('hex'));
108
+
109
+ // 3. Exchange code (callback) — include code_verifier
110
+ const tokenResponse = await fetch('https://provider.com/token', {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
113
+ body: new URLSearchParams({
114
+ grant_type: 'authorization_code',
115
+ code: authorizationCode,
116
+ redirect_uri: REDIRECT_URI,
117
+ client_id: CLIENT_ID,
118
+ code_verifier: codeVerifier, // Proves we initiated the flow
119
+ }),
120
+ });
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Encryption (Data at Rest)
126
+
127
+ ### AES-256-GCM (Recommended)
128
+
129
+ ```javascript
130
+ import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
131
+
132
+ const ALGORITHM = 'aes-256-gcm';
133
+ const KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex'); // 32 bytes
134
+
135
+ function encrypt(plaintext) {
136
+ const iv = randomBytes(16);
137
+ const cipher = createCipheriv(ALGORITHM, KEY, iv);
138
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
139
+ const tag = cipher.getAuthTag();
140
+ // Store: iv + tag + ciphertext (all needed for decryption)
141
+ return Buffer.concat([iv, tag, encrypted]).toString('base64');
142
+ }
143
+
144
+ function decrypt(encoded) {
145
+ const buffer = Buffer.from(encoded, 'base64');
146
+ const iv = buffer.subarray(0, 16);
147
+ const tag = buffer.subarray(16, 32);
148
+ const ciphertext = buffer.subarray(32);
149
+ const decipher = createDecipheriv(ALGORITHM, KEY, iv);
150
+ decipher.setAuthTag(tag);
151
+ return decipher.update(ciphertext) + decipher.final('utf8');
152
+ }
153
+ ```
154
+
155
+ ### Crypto Rules
156
+
157
+ | Use Case | Algorithm | BLOCK If |
158
+ |----------|-----------|----------|
159
+ | Password storage | Argon2id / bcrypt | MD5, SHA-*, plaintext |
160
+ | Data encryption | AES-256-GCM | AES-ECB, DES, RC4 |
161
+ | Integrity check | SHA-256 | MD5, SHA-1 |
162
+ | Signatures | HMAC-SHA256 | Custom MAC |
163
+ | Random tokens | `crypto.randomBytes()` | `Math.random()` |
164
+ | Key derivation | scrypt / HKDF | Simple hash |
165
+
166
+ ---
167
+
168
+ ## Fail-Closed Principle
169
+
170
+ Errors MUST deny access, never grant it:
171
+
172
+ ```javascript
173
+ // BAD: fail-open — error grants access
174
+ function authorize(user) {
175
+ try {
176
+ return checkPermission(user);
177
+ } catch {
178
+ return true; // BLOCK: error = access granted
179
+ }
180
+ }
181
+
182
+ // GOOD: fail-closed — error denies access
183
+ function authorize(user) {
184
+ try {
185
+ return checkPermission(user);
186
+ } catch {
187
+ return false; // Error = denied
188
+ }
189
+ }
190
+ ```
191
+
192
+ **BLOCK pattern**: Any catch/except block that returns `true`, `next()`, or allows continuation on auth/permission check failure.
@@ -0,0 +1,201 @@
1
+ # Desktop App Security Reference
2
+
3
+ > Loaded by `sentinel` when Electron or Tauri project detected (package.json contains `electron`, `@tauri-apps/cli`, or `tauri.conf.json` exists).
4
+
5
+ ---
6
+
7
+ ## Electron Security
8
+
9
+ ### Secure BrowserWindow Config
10
+
11
+ Every BrowserWindow MUST use:
12
+
13
+ ```javascript
14
+ const win = new BrowserWindow({
15
+ webPreferences: {
16
+ contextIsolation: true, // REQUIRED — separates preload from page
17
+ nodeIntegration: false, // REQUIRED — no Node in renderer
18
+ sandbox: true, // REQUIRED — OS-level sandbox
19
+ webSecurity: true, // REQUIRED — enforce same-origin
20
+ allowRunningInsecureContent: false,
21
+ experimentalFeatures: false,
22
+ }
23
+ });
24
+ ```
25
+
26
+ **BLOCK if any of these are wrong:**
27
+ - `contextIsolation: false` — renderer can access Node APIs
28
+ - `nodeIntegration: true` — XSS = full system compromise
29
+ - `sandbox: false` — no OS isolation
30
+ - `webSecurity: false` — disables same-origin policy
31
+
32
+ ### Preload Script (Minimal Surface)
33
+
34
+ Expose only specific functions, never blanket APIs:
35
+
36
+ ```javascript
37
+ // preload.js — GOOD: minimal, typed API
38
+ const { contextBridge, ipcRenderer } = require('electron');
39
+
40
+ contextBridge.exposeInMainWorld('api', {
41
+ readFile: (path) => ipcRenderer.invoke('fs:read', path),
42
+ saveFile: (path, data) => ipcRenderer.invoke('fs:save', path, data),
43
+ // NEVER expose: ipcRenderer.send, ipcRenderer.on, require, process
44
+ });
45
+ ```
46
+
47
+ ### IPC Validation
48
+
49
+ Validate BOTH sender identity and message content:
50
+
51
+ ```javascript
52
+ // main.js — validate IPC
53
+ ipcMain.handle('fs:read', async (event, filePath) => {
54
+ // 1. Verify sender
55
+ if (event.senderFrame.url !== 'app://./index.html') {
56
+ throw new Error('Unauthorized IPC sender');
57
+ }
58
+
59
+ // 2. Validate input
60
+ const resolved = path.resolve(filePath);
61
+ if (!resolved.startsWith(ALLOWED_DIR)) {
62
+ throw new Error('Path traversal blocked');
63
+ }
64
+
65
+ return fs.readFileSync(resolved, 'utf-8');
66
+ });
67
+ ```
68
+
69
+ ### Deep Link Validation
70
+
71
+ ```javascript
72
+ // Validate protocol handler input
73
+ app.setAsDefaultProtocolClient('myapp');
74
+
75
+ app.on('open-url', (event, url) => {
76
+ event.preventDefault();
77
+
78
+ const parsed = new URL(url);
79
+ const ALLOWED_HOSTS = ['app.example.com'];
80
+ const ALLOWED_PROTOCOLS = ['myapp:'];
81
+
82
+ if (!ALLOWED_PROTOCOLS.includes(parsed.protocol)) return;
83
+ if (parsed.host && !ALLOWED_HOSTS.includes(parsed.host)) return;
84
+ if (url.length > 2048) return; // Length limit
85
+
86
+ handleDeepLink(parsed);
87
+ });
88
+ ```
89
+
90
+ ### Electron Vulnerability Checklist
91
+
92
+ | Check | BLOCK if |
93
+ |-------|----------|
94
+ | `nodeIntegration` | `true` in any window |
95
+ | `contextIsolation` | `false` in any window |
96
+ | `sandbox` | `false` in any window |
97
+ | `webSecurity` | `false` |
98
+ | Preload exposes `ipcRenderer.send` | Direct IPC channel exposed |
99
+ | IPC handler missing sender check | No `event.senderFrame` validation |
100
+ | `shell.openExternal` unvalidated | User-controlled URLs passed directly |
101
+ | Auto-updater over HTTP | Not using HTTPS or missing signature check |
102
+ | `webview` tag without sandbox | Embedded content without isolation |
103
+ | Deep links unvalidated | No protocol/host allowlist |
104
+
105
+ ---
106
+
107
+ ## Tauri Security
108
+
109
+ ### Command Security
110
+
111
+ ```rust
112
+ // GOOD: Strict typing + validation
113
+ #[tauri::command]
114
+ fn read_file(path: String) -> Result<String, String> {
115
+ let canonical = std::fs::canonicalize(&path)
116
+ .map_err(|e| e.to_string())?;
117
+
118
+ // Validate path is within allowed directory
119
+ if !canonical.starts_with("/allowed/dir") {
120
+ return Err("Access denied".into());
121
+ }
122
+
123
+ std::fs::read_to_string(canonical)
124
+ .map_err(|e| e.to_string())
125
+ }
126
+ ```
127
+
128
+ ### Scope Configuration
129
+
130
+ ```json
131
+ // tauri.conf.json — restrictive scopes
132
+ {
133
+ "tauri": {
134
+ "allowlist": {
135
+ "fs": {
136
+ "scope": ["$APPDATA/*", "$RESOURCE/*"],
137
+ "readFile": true,
138
+ "writeFile": true
139
+ },
140
+ "http": {
141
+ "scope": ["https://api.example.com/*"]
142
+ },
143
+ "shell": {
144
+ "open": true,
145
+ "scope": []
146
+ }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ **BLOCK if:**
153
+ - `fs.scope: ["**"]` — unrestricted filesystem access
154
+ - `http.scope: ["https://**"]` — any HTTPS target
155
+ - `shell.scope` contains executable paths without validation
156
+
157
+ ### Tauri Vulnerability Checklist
158
+
159
+ | Check | BLOCK if |
160
+ |-------|----------|
161
+ | `fs.scope` | Contains `**` or `/` (unrestricted) |
162
+ | `http.scope` | Broad wildcard allowing arbitrary hosts |
163
+ | `shell` commands | Unrestricted `execute` scope |
164
+ | CSP missing | No Content-Security-Policy in `tauri.conf.json` |
165
+ | `invoke()` handler | Missing input validation or type checking |
166
+
167
+ ---
168
+
169
+ ## Common Desktop Patterns
170
+
171
+ ### Code Signing
172
+
173
+ Always sign releases — unsigned apps trigger OS warnings and can be tampered with:
174
+
175
+ - **Windows**: EV code signing certificate (hardware token)
176
+ - **macOS**: Developer ID certificate + notarization (`xcrun notarytool`)
177
+ - **Linux**: GPG signing for packages
178
+
179
+ ### Secure Auto-Update
180
+
181
+ ```
182
+ 1. Check for update over HTTPS
183
+ 2. Verify digital signature of update package
184
+ 3. Verify SHA-256 checksum
185
+ 4. Prompt user before installing (never silent)
186
+ 5. Graceful fallback if update fails
187
+ ```
188
+
189
+ ### Credential Storage
190
+
191
+ ```javascript
192
+ // Electron — use safeStorage (OS keychain)
193
+ const { safeStorage } = require('electron');
194
+
195
+ if (safeStorage.isEncryptionAvailable()) {
196
+ const encrypted = safeStorage.encryptString(token);
197
+ // Store encrypted buffer, never plaintext
198
+ }
199
+ ```
200
+
201
+ **BLOCK**: Storing credentials in `localStorage`, plain files, or `electron-store` without encryption.
@@ -0,0 +1,160 @@
1
+ # Supply Chain Security Reference
2
+
3
+ > Loaded by `sentinel` when dependency changes detected (package.json, package-lock.json, requirements.txt, Cargo.toml modified).
4
+
5
+ ---
6
+
7
+ ## Dependency Audit
8
+
9
+ ### Required Checks
10
+
11
+ On every dependency change, verify:
12
+
13
+ ```bash
14
+ # Node.js
15
+ npm audit --audit-level=high
16
+ npm audit signatures # Verify registry signatures
17
+
18
+ # Python
19
+ pip-audit
20
+ safety check
21
+
22
+ # Rust
23
+ cargo audit
24
+ ```
25
+
26
+ **BLOCK**: Any `high` or `critical` severity vulnerability in direct dependencies.
27
+ **WARN**: `high`/`critical` in transitive dependencies.
28
+
29
+ ---
30
+
31
+ ## Lock File Rules
32
+
33
+ | Rule | Severity |
34
+ |------|----------|
35
+ | Lock file not committed | BLOCK |
36
+ | Lock file deleted | BLOCK |
37
+ | Lock file modified without package.json change | WARN (investigate) |
38
+ | Multiple lock files (package-lock + yarn.lock) | WARN |
39
+
40
+ Lock files pin exact versions. Without them, builds are non-reproducible and vulnerable to supply chain attacks.
41
+
42
+ ---
43
+
44
+ ## Typosquatting Prevention
45
+
46
+ Before adding ANY new dependency, check:
47
+
48
+ | Signal | Example | Risk |
49
+ |--------|---------|------|
50
+ | Name differs by 1 char | `lodsh` vs `lodash` | Typosquat |
51
+ | Extra hyphen/underscore | `react-domm` | Typosquat |
52
+ | Scoped look-alike | `@react/core` vs `react` | Impersonation |
53
+ | Very new package | Published < 7 days ago | Suspicious |
54
+ | Low download count | < 100 weekly downloads | Unvetted |
55
+ | No repository URL | Missing `repository` field | Cannot verify source |
56
+
57
+ ### Scan Pattern
58
+
59
+ ```javascript
60
+ // sentinel should flag new dependencies added to package.json
61
+ // Check against known package names for close matches
62
+ const KNOWN_PACKAGES = ['lodash', 'express', 'react', 'axios', ...];
63
+
64
+ function checkTyposquat(name) {
65
+ for (const known of KNOWN_PACKAGES) {
66
+ if (levenshteinDistance(name, known) === 1) {
67
+ return { risk: 'HIGH', similar: known };
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Version Pinning Strategy
76
+
77
+ | Dependency Type | Strategy | Why |
78
+ |----------------|----------|-----|
79
+ | Direct (production) | `^` semver (default) | Balance updates + stability |
80
+ | Security-critical | Exact pin (`1.2.3`) | Prevent unexpected changes |
81
+ | Dev tools | `^` semver | Less risk, want updates |
82
+ | CDN resources | SRI hash required | Tamper detection |
83
+
84
+ ### Subresource Integrity (SRI)
85
+
86
+ For any CDN-loaded script or stylesheet:
87
+
88
+ ```html
89
+ <!-- GOOD: SRI hash verifies integrity -->
90
+ <script
91
+ src="https://cdn.example.com/lib.js"
92
+ integrity="sha384-abc123..."
93
+ crossorigin="anonymous"
94
+ ></script>
95
+
96
+ <!-- BLOCK: CDN resource without SRI -->
97
+ <script src="https://cdn.example.com/lib.js"></script>
98
+ ```
99
+
100
+ ---
101
+
102
+ ## npm Security Hardening
103
+
104
+ | Practice | Priority |
105
+ |----------|----------|
106
+ | Enable 2FA on npm account | BLOCK (maintainers) |
107
+ | Use scoped packages (`@org/pkg`) | Recommended |
108
+ | `npm audit` in CI pipeline | Required |
109
+ | `npm audit signatures` | Recommended |
110
+ | Review `postinstall` scripts | Required for new deps |
111
+ | Use `--ignore-scripts` for untrusted packages | Recommended |
112
+
113
+ ### Dangerous Install Scripts
114
+
115
+ **WARN** when a newly added package has `postinstall`, `preinstall`, or `install` scripts:
116
+
117
+ ```json
118
+ // package.json of dependency — review these carefully
119
+ {
120
+ "scripts": {
121
+ "postinstall": "node setup.js" // What does this run?
122
+ }
123
+ }
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Automated Dependency Updates
129
+
130
+ Configure one of:
131
+
132
+ ```yaml
133
+ # .github/dependabot.yml
134
+ version: 2
135
+ updates:
136
+ - package-ecosystem: npm
137
+ directory: /
138
+ schedule:
139
+ interval: weekly
140
+ open-pull-requests-limit: 10
141
+ reviewers:
142
+ - security-team
143
+ ```
144
+
145
+ **WARN** if no automated dependency update tool is configured (Dependabot, Renovate, or Snyk).
146
+
147
+ ---
148
+
149
+ ## Security Checklist for New Dependencies
150
+
151
+ Before approving a new dependency:
152
+
153
+ - [ ] Package name is correct (not a typosquat)
154
+ - [ ] Has significant download count (>1K/week for niche, >10K for common)
155
+ - [ ] Repository URL exists and matches claimed source
156
+ - [ ] No known vulnerabilities (`npm audit`)
157
+ - [ ] License is compatible
158
+ - [ ] Install scripts reviewed (if any)
159
+ - [ ] Maintainer has 2FA enabled (check npm profile)
160
+ - [ ] Not abandoned (last publish < 12 months)