fraim-framework 2.0.165 → 2.0.167

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 (55) hide show
  1. package/dist/src/ai-hub/catalog.js +20 -27
  2. package/dist/src/ai-hub/server.js +418 -2
  3. package/dist/src/ai-hub/word-sideload.js +95 -0
  4. package/dist/src/cli/commands/org.js +40 -0
  5. package/dist/src/cli/commands/test-mcp.js +171 -0
  6. package/dist/src/cli/fraim.js +2 -0
  7. package/dist/src/cli/setup/first-run.js +242 -0
  8. package/dist/src/cli/utils/org-publish.js +98 -0
  9. package/dist/src/config/ai-manager-hiring.js +121 -0
  10. package/dist/src/config/compat.js +16 -0
  11. package/dist/src/config/feature-flags.js +25 -0
  12. package/dist/src/config/persona-capability-bundles.js +273 -0
  13. package/dist/src/config/persona-hiring.js +270 -0
  14. package/dist/src/config/portfolio-slug-overrides.js +17 -0
  15. package/dist/src/config/pricing.js +37 -0
  16. package/dist/src/config/stripe.js +43 -0
  17. package/dist/src/core/config-loader.js +9 -5
  18. package/dist/src/core/config-writer.js +75 -0
  19. package/dist/src/core/fraim-config-schema.generated.js +0 -21
  20. package/dist/src/core/utils/job-aliases.js +47 -0
  21. package/dist/src/core/utils/local-registry-resolver.js +8 -1
  22. package/dist/src/core/utils/workflow-parser.js +174 -0
  23. package/index.js +1 -1
  24. package/package.json +5 -1
  25. package/public/ai-hub/index.html +81 -0
  26. package/public/ai-hub/powerpoint-taskpane/index.html +236 -236
  27. package/public/ai-hub/powerpoint-taskpane/manifest.xml +29 -29
  28. package/public/ai-hub/review.css +13 -0
  29. package/public/ai-hub/script.js +414 -4
  30. package/public/ai-hub/styles.css +56 -0
  31. package/public/first-run/styles.css +73 -73
  32. package/public/portfolio/ashley.html +523 -0
  33. package/public/portfolio/auditya.html +83 -0
  34. package/public/portfolio/banke.html +83 -0
  35. package/public/portfolio/beza.html +659 -0
  36. package/public/portfolio/careena.html +632 -0
  37. package/public/portfolio/casey.html +568 -0
  38. package/public/portfolio/celia.html +490 -0
  39. package/public/portfolio/deidre.html +642 -0
  40. package/public/portfolio/gautam.html +597 -0
  41. package/public/portfolio/hari.html +469 -0
  42. package/public/portfolio/huxley.html +1354 -0
  43. package/public/portfolio/index.html +741 -0
  44. package/public/portfolio/maestro.html +518 -0
  45. package/public/portfolio/mandy.html +590 -0
  46. package/public/portfolio/mona.html +597 -0
  47. package/public/portfolio/pam.html +887 -0
  48. package/public/portfolio/procella.html +107 -0
  49. package/public/portfolio/qasm.html +569 -0
  50. package/public/portfolio/ricardo.html +489 -0
  51. package/public/portfolio/sade.html +560 -0
  52. package/public/portfolio/sam.html +654 -0
  53. package/public/portfolio/sechar.html +580 -0
  54. package/public/portfolio/sreya.html +599 -0
  55. package/public/portfolio/swen.html +601 -0
@@ -0,0 +1,580 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="light">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SEChar | AI Security Engineer | FRAIM Portfolio</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ --accent: #ef4444;
13
+ --accent-2: #b91c1c;
14
+ --accent-soft: #fee2e2;
15
+ --bg: #fff7f7;
16
+ --surface: #ffffff;
17
+ --surface-2: #fff2f2;
18
+ --text: #240b0b;
19
+ --text-2: #4b5563;
20
+ --muted: #6b7280;
21
+ --border: #f3d2d2;
22
+ --shadow: 0 18px 44px rgba(120, 21, 21, 0.14);
23
+ }
24
+ [data-theme="dark"] {
25
+ --bg: #160707;
26
+ --surface: #251010;
27
+ --surface-2: #311515;
28
+ --text: #f8e9e9;
29
+ --text-2: #dbc5c5;
30
+ --muted: #bea1a1;
31
+ --border: #4b2020;
32
+ }
33
+ * { box-sizing: border-box; }
34
+ body {
35
+ margin: 0;
36
+ font-family: 'Inter', sans-serif;
37
+ background: radial-gradient(circle at top, rgba(239,68,68,0.14), transparent 28%), var(--bg);
38
+ color: var(--text);
39
+ }
40
+ .site-header {
41
+ position: sticky;
42
+ top: 0;
43
+ z-index: 20;
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: space-between;
47
+ padding: 16px 24px;
48
+ border-bottom: 1px solid var(--border);
49
+ background: rgba(255,255,255,0.88);
50
+ backdrop-filter: blur(12px);
51
+ }
52
+ [data-theme="dark"] .site-header { background: rgba(22,7,7,0.88); }
53
+ .brand {
54
+ display: inline-flex;
55
+ gap: 10px;
56
+ align-items: center;
57
+ color: var(--text);
58
+ text-decoration: none;
59
+ font-weight: 700;
60
+ }
61
+ .brand-logo {
62
+ width: 34px;
63
+ height: 34px;
64
+ border-radius: 10px;
65
+ background: linear-gradient(135deg, #ef4444, #f59e0b);
66
+ color: #fff;
67
+ display: grid;
68
+ place-items: center;
69
+ }
70
+ .theme-btn {
71
+ border: 1px solid var(--border);
72
+ background: var(--surface);
73
+ color: var(--text);
74
+ border-radius: 999px;
75
+ padding: 8px 12px;
76
+ cursor: pointer;
77
+ }
78
+ .hero,
79
+ .section,
80
+ .footer {
81
+ width: min(1080px, calc(100% - 32px));
82
+ margin: 0 auto;
83
+ }
84
+ .hero {
85
+ padding: 56px 0 28px;
86
+ }
87
+ .hero-grid {
88
+ display: grid;
89
+ grid-template-columns: 1.1fr 0.9fr;
90
+ gap: 22px;
91
+ }
92
+ .panel,
93
+ .card,
94
+ .artifact-shell {
95
+ background: var(--surface);
96
+ border: 1px solid var(--border);
97
+ border-radius: 28px;
98
+ box-shadow: var(--shadow);
99
+ }
100
+ .panel { padding: 30px; }
101
+ .chip {
102
+ display: inline-flex;
103
+ align-items: center;
104
+ padding: 7px 12px;
105
+ border-radius: 999px;
106
+ background: var(--accent-soft);
107
+ color: var(--accent-2);
108
+ font-size: 12px;
109
+ letter-spacing: 0.05em;
110
+ text-transform: uppercase;
111
+ font-weight: 700;
112
+ }
113
+ h1 {
114
+ margin: 18px 0 14px;
115
+ font-size: clamp(34px, 5vw, 62px);
116
+ line-height: 0.98;
117
+ letter-spacing: -0.05em;
118
+ }
119
+ .hero p,
120
+ .proof p,
121
+ .summary p {
122
+ color: var(--text-2);
123
+ line-height: 1.7;
124
+ }
125
+ .hero-metrics {
126
+ display: grid;
127
+ grid-template-columns: repeat(3, 1fr);
128
+ gap: 12px;
129
+ margin-top: 22px;
130
+ }
131
+ .metric {
132
+ background: var(--surface-2);
133
+ border-radius: 20px;
134
+ padding: 16px;
135
+ }
136
+ .metric strong {
137
+ display: block;
138
+ font-size: 22px;
139
+ margin-bottom: 6px;
140
+ }
141
+ .summary {
142
+ display: grid;
143
+ gap: 12px;
144
+ }
145
+ .summary-card {
146
+ background: var(--surface-2);
147
+ border-radius: 20px;
148
+ padding: 16px;
149
+ }
150
+ .summary-card strong {
151
+ display: block;
152
+ margin-bottom: 6px;
153
+ }
154
+ .section {
155
+ padding: 18px 0 72px;
156
+ }
157
+ .section-head {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 16px;
161
+ margin-bottom: 18px;
162
+ }
163
+ .section-head h2 {
164
+ margin: 0;
165
+ color: var(--muted);
166
+ text-transform: uppercase;
167
+ letter-spacing: 0.12em;
168
+ font-size: 13px;
169
+ }
170
+ .section-line {
171
+ flex: 1;
172
+ height: 1px;
173
+ background: var(--border);
174
+ }
175
+ .card-stack {
176
+ display: grid;
177
+ gap: 18px;
178
+ }
179
+ .card-header {
180
+ display: flex;
181
+ gap: 16px;
182
+ align-items: flex-start;
183
+ padding: 24px 24px 0;
184
+ cursor: pointer;
185
+ }
186
+ .card-icon {
187
+ width: 52px;
188
+ height: 52px;
189
+ border-radius: 16px;
190
+ display: grid;
191
+ place-items: center;
192
+ background: var(--accent-soft);
193
+ color: var(--accent-2);
194
+ font-family: 'JetBrains Mono', monospace;
195
+ font-size: 14px;
196
+ font-weight: 700;
197
+ }
198
+ .card-tag {
199
+ color: var(--accent-2);
200
+ text-transform: uppercase;
201
+ letter-spacing: 0.08em;
202
+ font-size: 12px;
203
+ font-weight: 700;
204
+ }
205
+ .card-title {
206
+ margin: 8px 0 6px;
207
+ font-size: 25px;
208
+ line-height: 1.08;
209
+ }
210
+ .card-subtitle {
211
+ color: var(--muted);
212
+ font-size: 13px;
213
+ }
214
+ .card-meta { flex: 1; }
215
+ .card-toggle {
216
+ color: var(--muted);
217
+ font-size: 28px;
218
+ }
219
+ .card-body {
220
+ display: none;
221
+ padding: 22px 24px 24px;
222
+ }
223
+ .card.open .card-body { display: block; }
224
+ .card.open .card-toggle { transform: rotate(90deg); }
225
+ .context {
226
+ margin: 0 0 16px;
227
+ background: var(--surface-2);
228
+ border-radius: 18px;
229
+ padding: 16px 18px;
230
+ color: var(--text-2);
231
+ line-height: 1.7;
232
+ }
233
+ .proof-grid {
234
+ display: grid;
235
+ grid-template-columns: repeat(3, 1fr);
236
+ gap: 12px;
237
+ margin-bottom: 16px;
238
+ }
239
+ .proof {
240
+ background: var(--surface-2);
241
+ border-radius: 18px;
242
+ padding: 16px;
243
+ }
244
+ .proof-label {
245
+ color: var(--muted);
246
+ text-transform: uppercase;
247
+ letter-spacing: 0.1em;
248
+ font-size: 11px;
249
+ font-weight: 700;
250
+ }
251
+ .artifact-label {
252
+ margin: 0 0 12px;
253
+ color: var(--muted);
254
+ text-transform: uppercase;
255
+ letter-spacing: 0.08em;
256
+ font-size: 12px;
257
+ font-weight: 700;
258
+ }
259
+ .artifact-shell { padding: 18px; }
260
+ .matrix,
261
+ .queue {
262
+ width: 100%;
263
+ border-collapse: collapse;
264
+ font-size: 14px;
265
+ }
266
+ .matrix th,
267
+ .matrix td,
268
+ .queue th,
269
+ .queue td {
270
+ border-bottom: 1px solid var(--border);
271
+ padding: 11px 10px;
272
+ text-align: left;
273
+ }
274
+ .matrix th,
275
+ .queue th {
276
+ color: var(--muted);
277
+ text-transform: uppercase;
278
+ letter-spacing: 0.08em;
279
+ font-size: 11px;
280
+ }
281
+ .badge {
282
+ display: inline-flex;
283
+ padding: 5px 10px;
284
+ border-radius: 999px;
285
+ font-size: 12px;
286
+ font-weight: 700;
287
+ }
288
+ .badge-red { background: var(--accent-soft); color: var(--accent-2); }
289
+ .badge-green { background: #dcfce7; color: #166534; }
290
+ .badge-amber { background: #fef3c7; color: #b45309; }
291
+ .badge-blue { background: #dbeafe; color: #1d4ed8; }
292
+ .pill-row {
293
+ display: flex;
294
+ gap: 10px;
295
+ flex-wrap: wrap;
296
+ }
297
+ .pill {
298
+ padding: 10px 12px;
299
+ border-radius: 14px;
300
+ background: var(--surface-2);
301
+ color: var(--text-2);
302
+ font-size: 13px;
303
+ }
304
+ .note {
305
+ margin-top: 14px;
306
+ color: var(--muted);
307
+ font-size: 12px;
308
+ }
309
+ .footer {
310
+ padding: 0 0 44px;
311
+ text-align: center;
312
+ color: var(--muted);
313
+ font-size: 13px;
314
+ }
315
+ .footer a { color: var(--accent-2); text-decoration: none; }
316
+ @media (max-width: 840px) {
317
+ .hero-grid,
318
+ .hero-metrics,
319
+ .proof-grid {
320
+ grid-template-columns: 1fr;
321
+ }
322
+ }
323
+ </style>
324
+ </head>
325
+ <body>
326
+ <header class="site-header">
327
+ <a class="brand" href="/">
328
+ <span class="brand-logo">F</span>
329
+ <span>FRAIM</span>
330
+ </a>
331
+ <button class="theme-btn" type="button" onclick="toggleTheme()">Theme</button>
332
+ </header>
333
+
334
+ <section class="hero">
335
+ <div class="hero-grid">
336
+ <div class="panel">
337
+ <div class="chip">AI Security Engineer</div>
338
+ <h1>Setup the baseline. Run the queue. Clear the risk.</h1>
339
+ <p>SEChar sets the launch baseline, keeps the findings queue coherent, and still handles the diff-scoped reviews and remediation calls teams expect from an applied security partner.</p>
340
+ <div class="hero-metrics">
341
+ <div class="metric">
342
+ <strong>2 operating lanes</strong>
343
+ launch baseline plus findings command
344
+ </div>
345
+ <div class="metric">
346
+ <strong>1 ledger</strong>
347
+ for scanner output, reviews, and incident follow-up
348
+ </div>
349
+ <div class="metric">
350
+ <strong>Launch ready</strong>
351
+ with explicit control and evidence expectations
352
+ </div>
353
+ </div>
354
+ </div>
355
+ <aside class="panel summary">
356
+ <div class="summary-card">
357
+ <strong>AI-Native Security Setup</strong>
358
+ Control baseline for auth, secrets, environments, logging, and release gates.
359
+ </div>
360
+ <div class="summary-card">
361
+ <strong>Security Findings Command Center</strong>
362
+ One remediation queue with owners, deadlines, and disposition discipline.
363
+ </div>
364
+ <div class="summary-card">
365
+ <strong>Still ships the classics</strong>
366
+ Diff-scoped security review, vulnerability triage, and access-control hardening.
367
+ </div>
368
+ </aside>
369
+ </div>
370
+ </section>
371
+
372
+ <section class="section">
373
+ <div class="section-head">
374
+ <h2>Selected Work</h2>
375
+ <div class="section-line"></div>
376
+ </div>
377
+ <div class="card-stack">
378
+ <article class="card open" id="card1">
379
+ <div class="card-header" onclick="toggleCard(1)">
380
+ <div class="card-icon">REV</div>
381
+ <div class="card-meta">
382
+ <div class="card-tag">Security Review</div>
383
+ <div class="card-title">Diff-scoped review that does not stall the team</div>
384
+ <div class="card-subtitle">FRAIM | security-review</div>
385
+ </div>
386
+ <div class="card-toggle">&rsaquo;</div>
387
+ </div>
388
+ <div class="card-body">
389
+ <p class="context">The team needed a fast review of the exact surfaces they were about to ship, not a slow audit of every system they had ever touched. SEChar scoped the work to the real attack surface and cleared the release with focused evidence.</p>
390
+ <div class="proof-grid">
391
+ <div class="proof">
392
+ <div class="proof-label">Scope</div>
393
+ <p>Diff-only review across web and auth-touching code.</p>
394
+ </div>
395
+ <div class="proof">
396
+ <div class="proof-label">Method</div>
397
+ <p>Threat-surface classification first, then targeted checks for the categories actually present.</p>
398
+ </div>
399
+ <div class="proof">
400
+ <div class="proof-label">Outcome</div>
401
+ <p>The release cleared review with evidence instead of security theatre.</p>
402
+ </div>
403
+ </div>
404
+ <div class="artifact-label">Live Artifact | Review coverage matrix</div>
405
+ <div class="artifact-shell">
406
+ <table class="matrix">
407
+ <thead>
408
+ <tr><th>Category</th><th>Surface</th><th>Status</th></tr>
409
+ </thead>
410
+ <tbody>
411
+ <tr><td>Access control</td><td>Auth middleware</td><td><span class="badge badge-green">Cleared</span></td></tr>
412
+ <tr><td>Injection</td><td>Public HTML</td><td><span class="badge badge-green">Cleared</span></td></tr>
413
+ <tr><td>Secrets</td><td>Config and demo values</td><td><span class="badge badge-green">Cleared</span></td></tr>
414
+ <tr><td>Privacy</td><td>Synthetic data only</td><td><span class="badge badge-green">Cleared</span></td></tr>
415
+ </tbody>
416
+ </table>
417
+ </div>
418
+ </div>
419
+ </article>
420
+
421
+ <article class="card" id="card2">
422
+ <div class="card-header" onclick="toggleCard(2)">
423
+ <div class="card-icon">SET</div>
424
+ <div class="card-meta">
425
+ <div class="card-tag">AI-Native Security Setup</div>
426
+ <div class="card-title">Baseline controls before the first trust claim goes public</div>
427
+ <div class="card-subtitle">FRAIM | ai-native-security-setup</div>
428
+ </div>
429
+ <div class="card-toggle">&rsaquo;</div>
430
+ </div>
431
+ <div class="card-body">
432
+ <p class="context">An AI-native product wanted to talk about enterprise readiness, but the control floor was still implicit. SEChar turned the vague ask into a launch-sequenced baseline for identity, secrets, environments, logging, and evidence hooks.</p>
433
+ <div class="proof-grid">
434
+ <div class="proof">
435
+ <div class="proof-label">Threat model</div>
436
+ <p>Web, API, model workflow, prompt/context handling, and operator controls.</p>
437
+ </div>
438
+ <div class="proof">
439
+ <div class="proof-label">Must-have controls</div>
440
+ <p>Role-based access, secret separation, release review, dependency hygiene, and event logging.</p>
441
+ </div>
442
+ <div class="proof">
443
+ <div class="proof-label">Launch posture</div>
444
+ <p>Clear distinction between blockers, next-phase hardening, and future compliance evidence.</p>
445
+ </div>
446
+ </div>
447
+ <div class="artifact-label">Live Artifact | Setup baseline</div>
448
+ <div class="artifact-shell">
449
+ <div class="pill-row">
450
+ <span class="pill">Identity review for operators and service accounts</span>
451
+ <span class="pill">Secrets split by environment and owner</span>
452
+ <span class="pill">Release gate before public launch</span>
453
+ <span class="pill">Audit event coverage for security-relevant actions</span>
454
+ <span class="pill">Evidence hooks for later audit packages</span>
455
+ </div>
456
+ </div>
457
+ </div>
458
+ </article>
459
+
460
+ <article class="card" id="card3">
461
+ <div class="card-header" onclick="toggleCard(3)">
462
+ <div class="card-icon">CMD</div>
463
+ <div class="card-meta">
464
+ <div class="card-tag">Security Findings Command Center</div>
465
+ <div class="card-title">One queue for scanner noise, real risk, and actual owners</div>
466
+ <div class="card-subtitle">FRAIM | security-findings-command-center</div>
467
+ </div>
468
+ <div class="card-toggle">&rsaquo;</div>
469
+ </div>
470
+ <div class="card-body">
471
+ <p class="context">The team had findings in Slack threads, scanner dashboards, and one-off review comments. SEChar collapsed the noise into one command center so engineering leadership could see what mattered and what was already dispositioned.</p>
472
+ <div class="proof-grid">
473
+ <div class="proof">
474
+ <div class="proof-label">Intake</div>
475
+ <p>Scanners, reviews, incidents, and audit asks normalized into one ledger.</p>
476
+ </div>
477
+ <div class="proof">
478
+ <div class="proof-label">Prioritization</div>
479
+ <p>Exploitability, blast radius, customer exposure, and deadline pressure.</p>
480
+ </div>
481
+ <div class="proof">
482
+ <div class="proof-label">Resolution</div>
483
+ <p>Every active item gets an owner, due date, and proof-of-closure expectation.</p>
484
+ </div>
485
+ </div>
486
+ <div class="artifact-label">Live Artifact | Findings queue</div>
487
+ <div class="artifact-shell">
488
+ <table class="queue">
489
+ <thead>
490
+ <tr><th>Finding</th><th>Risk</th><th>Owner</th><th>Status</th></tr>
491
+ </thead>
492
+ <tbody>
493
+ <tr><td>Prototype pollution in user-input path</td><td>Exploitability confirmed</td><td>Platform team</td><td><span class="badge badge-red">Patch now</span></td></tr>
494
+ <tr><td>Dependency SSRF alert with no attack path</td><td>Theoretical in current architecture</td><td>Security</td><td><span class="badge badge-blue">Accepted with rationale</span></td></tr>
495
+ <tr><td>Missing audit events for admin role changes</td><td>High trust gap</td><td>Backend lead</td><td><span class="badge badge-amber">This sprint</span></td></tr>
496
+ <tr><td>Outdated low-risk library</td><td>Low blast radius</td><td>Infra</td><td><span class="badge badge-green">Scheduled</span></td></tr>
497
+ </tbody>
498
+ </table>
499
+ </div>
500
+ </div>
501
+ </article>
502
+
503
+ <article class="card" id="card4">
504
+ <div class="card-header" onclick="toggleCard(4)">
505
+ <div class="card-icon">CVE</div>
506
+ <div class="card-meta">
507
+ <div class="card-tag">Vulnerability Triage</div>
508
+ <div class="card-title">Critical fix first, noise second</div>
509
+ <div class="card-subtitle">FRAIM | vulnerability-triage-and-remediation</div>
510
+ </div>
511
+ <div class="card-toggle">&rsaquo;</div>
512
+ </div>
513
+ <div class="card-body">
514
+ <p class="context">Not every scanner alert deserves the same reaction. SEChar evaluates whether the attack path is real before forcing the team into churn.</p>
515
+ <div class="proof-grid">
516
+ <div class="proof">
517
+ <div class="proof-label">Critical</div>
518
+ <p>User-input path affected, patch immediately.</p>
519
+ </div>
520
+ <div class="proof">
521
+ <div class="proof-label">Deferred</div>
522
+ <p>No attack path in the current product shape.</p>
523
+ </div>
524
+ <div class="proof">
525
+ <div class="proof-label">Scheduled</div>
526
+ <p>Real but low-blast-radius issue queued behind higher-risk work.</p>
527
+ </div>
528
+ </div>
529
+ </div>
530
+ </article>
531
+
532
+ <article class="card" id="card5">
533
+ <div class="card-header" onclick="toggleCard(5)">
534
+ <div class="card-icon">IAM</div>
535
+ <div class="card-meta">
536
+ <div class="card-tag">Access Control</div>
537
+ <div class="card-title">Mixed-identity handling that rejects ambiguity instead of guessing</div>
538
+ <div class="card-subtitle">FRAIM | auth hardening</div>
539
+ </div>
540
+ <div class="card-toggle">&rsaquo;</div>
541
+ </div>
542
+ <div class="card-body">
543
+ <p class="context">When both an API key and a session cookie show up on the same request, the wrong default can become a real privilege-mixing bug. SEChar resolved the ambiguity explicitly.</p>
544
+ <div class="artifact-label">Live Artifact | Decision rules</div>
545
+ <div class="artifact-shell">
546
+ <div class="pill-row">
547
+ <span class="pill">Resolve both identities when multiple credentials are present</span>
548
+ <span class="pill">Reject on mismatch instead of picking a winner</span>
549
+ <span class="pill">Write an audit event for every mixed-identity rejection</span>
550
+ <span class="pill">Preserve simple single-credential paths when only one identity exists</span>
551
+ </div>
552
+ <div class="note">SEChar combines setup decisions, queue ownership, remediation judgment, and hardening rules in one operating surface instead of treating them as disconnected security chores.</div>
553
+ </div>
554
+ </div>
555
+ </article>
556
+ </div>
557
+ </section>
558
+
559
+ <footer class="footer">
560
+ Part of <a href="/">FRAIM</a> | <a href="/portfolio/index.html">View all employees</a>
561
+ </footer>
562
+
563
+ <script>
564
+ function toggleTheme() {
565
+ const html = document.documentElement;
566
+ html.setAttribute('data-theme', html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark');
567
+ }
568
+
569
+ function toggleCard(num) {
570
+ const card = document.getElementById('card' + num);
571
+ const willOpen = !card.classList.contains('open');
572
+ document.querySelectorAll('.card').forEach((item) => item.classList.remove('open'));
573
+ if (willOpen) {
574
+ card.classList.add('open');
575
+ card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
576
+ }
577
+ }
578
+ </script>
579
+ </body>
580
+ </html>