@rune-kit/rune 2.3.3 → 2.6.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 +86 -17
- package/compiler/__tests__/pack-split.test.js +141 -1
- package/compiler/__tests__/parser.test.js +147 -55
- package/compiler/__tests__/scripts-bundling.test.js +283 -0
- package/compiler/__tests__/skill-index.test.js +218 -0
- package/compiler/__tests__/tier-override.test.js +41 -0
- package/compiler/adapters/antigravity.js +71 -53
- 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/bin/rune.js +355 -355
- package/compiler/doctor.js +11 -1
- package/compiler/emitter.js +678 -386
- package/compiler/parser.js +267 -247
- 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/hooks/hooks.json +12 -0
- package/hooks/intent-router/index.cjs +108 -0
- package/hooks/pre-tool-guard/index.cjs +177 -68
- package/package.json +63 -64
- package/skills/brainstorm/SKILL.md +2 -0
- package/skills/cook/SKILL.md +661 -648
- package/skills/debug/SKILL.md +394 -392
- package/skills/deploy/SKILL.md +2 -0
- package/skills/fix/SKILL.md +283 -281
- package/skills/marketing/SKILL.md +3 -0
- package/skills/onboard/SKILL.md +7 -0
- package/skills/plan/SKILL.md +344 -342
- package/skills/preflight/SKILL.md +362 -360
- package/skills/review/SKILL.md +491 -489
- package/skills/scout/SKILL.md +1 -0
- package/skills/sentinel/SKILL.md +319 -296
- 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/session-bridge/SKILL.md +1 -0
- package/skills/slides/SKILL.md +142 -0
- package/skills/slides/scripts/build-deck.js +158 -0
- package/skills/team/SKILL.md +1 -0
- package/skills/test/SKILL.md +587 -585
- package/skills/verification/SKILL.md +1 -0
- package/skills/watchdog/SKILL.md +2 -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
|
@@ -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)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slides
|
|
3
|
+
description: Generate Marp-compatible slide decks from structured JSON schema. Converts context into presentations for tech talks, sprint demos, and tutorials.
|
|
4
|
+
metadata:
|
|
5
|
+
author: runedev
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
layer: L3
|
|
8
|
+
model: sonnet
|
|
9
|
+
group: media
|
|
10
|
+
tools: "Read, Write, Bash"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# slides
|
|
14
|
+
|
|
15
|
+
## Purpose
|
|
16
|
+
|
|
17
|
+
Generates structured slide decks as Marp-compatible markdown. The agent analyzes context (feature, sprint, tutorial) and produces a JSON slide schema, then calls `build-deck.js` to convert it to presentation-ready markdown. The script standardizes output format — preventing agent freestyle errors when context runs low.
|
|
18
|
+
|
|
19
|
+
## Triggers
|
|
20
|
+
|
|
21
|
+
- User says "create slides", "make presentation", "demo deck", "sprint review slides", "tech talk"
|
|
22
|
+
- `/rune slides` — manual invocation
|
|
23
|
+
- Called by other skills needing presentation output
|
|
24
|
+
|
|
25
|
+
## Called By (inbound)
|
|
26
|
+
|
|
27
|
+
- `marketing` (L2): launch presentations, product demos
|
|
28
|
+
- `video-creator` (L3): slide-based video storyboards
|
|
29
|
+
- User: direct invocation
|
|
30
|
+
|
|
31
|
+
## Calls (outbound)
|
|
32
|
+
|
|
33
|
+
None — pure L3 utility.
|
|
34
|
+
|
|
35
|
+
## Executable Instructions
|
|
36
|
+
|
|
37
|
+
### Step 1: Analyze Context
|
|
38
|
+
|
|
39
|
+
Determine the presentation type from the user's request:
|
|
40
|
+
|
|
41
|
+
| Context | Template | Typical Slides |
|
|
42
|
+
|---------|----------|---------------|
|
|
43
|
+
| Feature demo | Demo walkthrough | Problem → Solution → Architecture → Live demo → Next steps |
|
|
44
|
+
| Sprint review | Sprint summary | Goals → Completed → Metrics → Blockers → Next sprint |
|
|
45
|
+
| Tech talk | Teaching format | Hook → Concept → Deep dive → Code examples → Takeaways |
|
|
46
|
+
| Tutorial | Step-by-step | Intro → Prerequisites → Step 1-N → Summary → Resources |
|
|
47
|
+
| Pitch | Persuasion | Problem → Market → Solution → Traction → Ask |
|
|
48
|
+
|
|
49
|
+
### Step 2: Generate JSON Schema
|
|
50
|
+
|
|
51
|
+
Create a JSON file following this schema:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"title": "Presentation Title",
|
|
56
|
+
"author": "Author Name (optional)",
|
|
57
|
+
"theme": "default|gaia|uncover",
|
|
58
|
+
"slides": [
|
|
59
|
+
{
|
|
60
|
+
"type": "title|content|code|diagram|image|quote|section",
|
|
61
|
+
"heading": "Slide Heading",
|
|
62
|
+
"body": "Markdown body text",
|
|
63
|
+
"notes": "Speaker notes (optional)",
|
|
64
|
+
"code": { "lang": "javascript", "source": "console.log('hi')" },
|
|
65
|
+
"diagram": "graph LR; A-->B"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Slide types:**
|
|
72
|
+
- `title` — opening slide with `# heading`
|
|
73
|
+
- `content` — standard slide with `## heading` + body
|
|
74
|
+
- `code` — slide with syntax-highlighted code block
|
|
75
|
+
- `diagram` — slide with Mermaid diagram
|
|
76
|
+
- `image` — slide with image reference in body
|
|
77
|
+
- `quote` — slide with blockquote formatting
|
|
78
|
+
- `section` — section divider with `# heading`
|
|
79
|
+
|
|
80
|
+
Save the JSON to a temporary file (e.g., `slides.json`).
|
|
81
|
+
|
|
82
|
+
### Step 3: Build Deck
|
|
83
|
+
|
|
84
|
+
Execute the build script:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
node {scripts_dir}/build-deck.js --input slides.json --output deck.md
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The script outputs Marp-compatible markdown with:
|
|
91
|
+
- Marp frontmatter (`marp: true`, theme)
|
|
92
|
+
- Slide separators (`---`)
|
|
93
|
+
- Speaker notes as HTML comments
|
|
94
|
+
- Code blocks with language hints
|
|
95
|
+
- Mermaid diagram blocks
|
|
96
|
+
|
|
97
|
+
### Step 4: Present Result
|
|
98
|
+
|
|
99
|
+
Show the user:
|
|
100
|
+
1. The generated `deck.md` file path
|
|
101
|
+
2. How to preview: `npx @marp-team/marp-cli deck.md --preview` (or any Marp viewer)
|
|
102
|
+
3. How to export PDF: `npx @marp-team/marp-cli deck.md --pdf`
|
|
103
|
+
|
|
104
|
+
**Fallback**: If `{scripts_dir}/build-deck.js` is unavailable, generate the Marp markdown directly — the script is an optimization, not a hard dependency.
|
|
105
|
+
|
|
106
|
+
## Output Format
|
|
107
|
+
|
|
108
|
+
```markdown
|
|
109
|
+
---
|
|
110
|
+
marp: true
|
|
111
|
+
theme: default
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
# Presentation Title
|
|
115
|
+
|
|
116
|
+
Author Name
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Slide Heading
|
|
121
|
+
|
|
122
|
+
Slide body content
|
|
123
|
+
|
|
124
|
+
<!-- notes: Speaker notes here -->
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Sharp Edges
|
|
128
|
+
|
|
129
|
+
| Failure Mode | Severity | Mitigation |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| build-deck.js not found | LOW | Agent generates Marp markdown directly (fallback) |
|
|
132
|
+
| Invalid JSON input | MEDIUM | Script exits 1 with parse error — agent fixes JSON and retries |
|
|
133
|
+
| Marp not installed | LOW | Script outputs plain .md — user installs Marp CLI separately |
|
|
134
|
+
| Too many slides (>30) | MEDIUM | Agent should split into multiple decks or summarize |
|
|
135
|
+
|
|
136
|
+
## Constraints
|
|
137
|
+
|
|
138
|
+
1. Output MUST be valid Marp markdown (parseable by `@marp-team/marp-cli`)
|
|
139
|
+
2. DO NOT embed build-deck.js source in this skill — call it via `{scripts_dir}`
|
|
140
|
+
3. DO NOT require Marp installation — output is standard markdown that Marp can consume
|
|
141
|
+
4. Keep slide count reasonable (5-15 for demos, 10-25 for talks)
|
|
142
|
+
5. Always include speaker notes for non-trivial slides
|