@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.
- package/README.md +51 -1
- package/compiler/__tests__/scripts-bundling.test.js +284 -0
- package/compiler/__tests__/tier-override.test.js +41 -0
- package/compiler/adapters/antigravity.js +4 -0
- package/compiler/adapters/codex.js +4 -0
- package/compiler/adapters/cursor.js +4 -0
- package/compiler/adapters/generic.js +4 -0
- package/compiler/adapters/openclaw.js +4 -0
- package/compiler/adapters/opencode.js +4 -0
- package/compiler/adapters/windsurf.js +4 -0
- package/compiler/emitter.js +85 -5
- package/compiler/transforms/scripts-path.js +18 -0
- package/extensions/zalo/PACK.md +20 -1
- package/extensions/zalo/references/conversation-management.md +214 -0
- package/extensions/zalo/references/eval-scenarios.md +157 -0
- package/extensions/zalo/references/listen-mode.md +237 -0
- package/extensions/zalo/references/mcp-production.md +274 -0
- package/extensions/zalo/references/multi-account-proxy.md +224 -0
- package/extensions/zalo/references/vietqr-banking.md +160 -0
- package/package.json +2 -3
- package/skills/marketing/SKILL.md +3 -0
- package/skills/sentinel/SKILL.md +4 -1
- package/skills/sentinel/references/auth-crypto-reference.md +192 -0
- package/skills/sentinel/references/desktop-security.md +201 -0
- package/skills/sentinel/references/supply-chain.md +160 -0
- package/skills/slides/SKILL.md +142 -0
- package/skills/slides/scripts/build-deck.js +158 -0
- package/docs/ANTIGRAVITY-GAP-ANALYSIS.md +0 -369
- package/docs/ARCHITECTURE.md +0 -332
- package/docs/COMMUNITY-PACKS.md +0 -109
- package/docs/CONTRIBUTING-L4.md +0 -215
- package/docs/CROSS-IDE-ANALYSIS.md +0 -164
- package/docs/EXTENSION-TEMPLATE.md +0 -126
- package/docs/MESH-RULES.md +0 -34
- package/docs/MULTI-PLATFORM.md +0 -804
- package/docs/SKILL-DEPTH-AUDIT.md +0 -191
- package/docs/SKILL-TEMPLATE.md +0 -118
- package/docs/TRADE-MATRIX.md +0 -327
- package/docs/VERSIONING.md +0 -91
- package/docs/VISION.md +0 -263
- package/docs/assets/demo-subtitles.srt +0 -215
- package/docs/assets/end-card.html +0 -276
- package/docs/assets/mesh-diagram.html +0 -654
- package/docs/assets/thumbnail.html +0 -295
- package/docs/guides/cli.md +0 -403
- package/docs/guides/index.html +0 -1450
- package/docs/index.html +0 -1005
- package/docs/references/claudekit-analysis.md +0 -414
- package/docs/references/voltagent-analysis.md +0 -189
- package/docs/script.js +0 -495
- package/docs/skills/index.html +0 -832
- package/docs/style.css +0 -958
- package/docs/video-demo-plan.md +0 -172
package/skills/sentinel/SKILL.md
CHANGED
|
@@ -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.
|
|
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)
|