@aegis-scan/skills 0.2.0 → 0.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 (45) hide show
  1. package/ATTRIBUTION.md +60 -4
  2. package/CHANGELOG.md +78 -0
  3. package/README.md +27 -0
  4. package/dist/bin.js +1 -1
  5. package/dist/commands/list.d.ts.map +1 -1
  6. package/dist/commands/list.js +9 -2
  7. package/dist/commands/list.js.map +1 -1
  8. package/dist/skills-loader.d.ts +43 -0
  9. package/dist/skills-loader.d.ts.map +1 -1
  10. package/dist/skills-loader.js +102 -0
  11. package/dist/skills-loader.js.map +1 -1
  12. package/package.json +1 -1
  13. package/sbom.cdx.json +1 -1
  14. package/skills/compliance/_INDEX.md +49 -0
  15. package/skills/compliance/aegis-native/brutaler-anwalt/SKILL.md +100 -3
  16. package/skills/defensive/aegis-native/rls-defense/SKILL.md +25 -0
  17. package/skills/defensive/aegis-native/tenant-isolation-defense/SKILL.md +26 -0
  18. package/skills/foundation/_INDEX.md +73 -0
  19. package/skills/foundation/aegis-native/aegis-audit/SKILL.md +194 -0
  20. package/skills/foundation/aegis-native/aegis-audit/references/layer-1-headers.md +138 -0
  21. package/skills/foundation/aegis-native/aegis-audit/references/layer-2-html.md +153 -0
  22. package/skills/foundation/aegis-native/aegis-audit/references/layer-3-impressum.md +159 -0
  23. package/skills/foundation/aegis-native/aegis-audit/references/layer-4-dse.md +178 -0
  24. package/skills/foundation/aegis-native/aegis-audit/references/layer-5-cookie.md +180 -0
  25. package/skills/foundation/aegis-native/aegis-audit/references/layer-6-branche.md +204 -0
  26. package/skills/foundation/aegis-native/aegis-audit/references/layer-7-code-cross-check.md +212 -0
  27. package/skills/foundation/aegis-native/aegis-audit/references/layer-8-schadens-diagnose.md +232 -0
  28. package/skills/foundation/aegis-native/aegis-customer-build/SKILL.md +232 -0
  29. package/skills/foundation/aegis-native/aegis-customer-build/references/phase-1-recon.md +147 -0
  30. package/skills/foundation/aegis-native/aegis-customer-build/references/phase-2-architecture.md +164 -0
  31. package/skills/foundation/aegis-native/aegis-customer-build/references/phase-3-component-build.md +231 -0
  32. package/skills/foundation/aegis-native/aegis-customer-build/references/phase-4-content.md +196 -0
  33. package/skills/foundation/aegis-native/aegis-customer-build/references/phase-5-integration.md +273 -0
  34. package/skills/foundation/aegis-native/aegis-customer-build/references/phase-6-mid-audit.md +200 -0
  35. package/skills/foundation/aegis-native/aegis-customer-build/references/phase-7-final-verify.md +258 -0
  36. package/skills/foundation/aegis-native/aegis-handover-writer/SKILL.md +128 -0
  37. package/skills/foundation/aegis-native/aegis-module-builder/SKILL.md +251 -0
  38. package/skills/foundation/aegis-native/aegis-orchestrator/SKILL.md +146 -0
  39. package/skills/foundation/aegis-native/aegis-quality-gates/SKILL.md +122 -0
  40. package/skills/foundation/aegis-native/aegis-skill-creator/SKILL.md +223 -0
  41. package/skills/foundation/aegis-native/aegis-skill-creator/references/hard-constraint-template.md +213 -0
  42. package/skills/foundation/aegis-native/aegis-skill-creator/references/skillforge-methodology.md +220 -0
  43. package/skills/foundation/aegis-native/dsgvo-compliance/SKILL.md +185 -0
  44. package/skills/foundation/aegis-native/dsgvo-compliance/references/art-13-15-templates.md +309 -0
  45. package/skills/foundation/aegis-native/dsgvo-compliance/references/datenpanne-runbook.md +291 -0
@@ -0,0 +1,231 @@
1
+ # Phase 3 Reference — Component-Build (Library-Binding + Per-Page Iteration)
2
+
3
+ Phase 3 is the longest phase (60-90 min). Outputs: a working `app/` (or framework-equivalent) directory with one file per page, each page binding library-components + page-level components per the architecture.md.
4
+
5
+ **Subagent dispatch:** strongly recommended. One Executor-subagent (model: sonnet) per page-batch.
6
+
7
+ ---
8
+
9
+ ## Library-Binding Workflow
10
+
11
+ Before iterating pages, scan the project's component-library inventory:
12
+
13
+ ```
14
+ 1. Read library-inventory: <library-root>/components/*.tsx (or framework-equivalent)
15
+ 2. Build map: { component-name: { props-shape, suggested-use-cases, dependencies } }
16
+ 3. Cross-check each briefing page's sections against library-components
17
+ 4. For each section in each page, choose: library-bound vs custom page-level
18
+ ```
19
+
20
+ **Heuristic for library-vs-custom:**
21
+
22
+ | Section type | Default |
23
+ |---|---|
24
+ | Hero with image + headline + CTA | library-bound (HeroBlock) |
25
+ | Logo-bar / press-mentions | library-bound (LogoBar) |
26
+ | Feature-grid (3-6 features) | library-bound (FeatureGrid) |
27
+ | Testimonials carousel | library-bound (Testimonials) |
28
+ | Pricing-table (≥ 2 tiers) | library-bound (PricingTable) |
29
+ | FAQ accordion | library-bound (FaqAccordion) |
30
+ | CTA banner | library-bound (CtaBanner) |
31
+ | Founder-story / origin-narrative | page-level (custom prose) |
32
+ | Industry-specific module (e.g., scanner-widget for security business) | page-level (project-specific) |
33
+ | Custom data-visualization | page-level |
34
+
35
+ **Rule:** ≥ 60% of sections per page should be library-bound. Higher = better (consistent quality-bar). If a page is < 60% library-bound, flag for operator review.
36
+
37
+ ---
38
+
39
+ ## Per-Page Iteration Pattern
40
+
41
+ For each page in `briefing.pages[]`:
42
+
43
+ ```
44
+ 1. Create file at app/<slug>/page.tsx (or root for slug == "home")
45
+ 2. Pull metadata from briefing-parsed.json[<slug>]
46
+ 3. Generate metadata export: { title, description, canonical }
47
+ 4. Compose JSX:
48
+ a. Import library-bound components for sections marked library
49
+ b. Define page-level components for sections marked custom
50
+ c. Order sections per briefing's page.sections[]
51
+ 5. Apply props per briefing-parsed.json (headlines, copy stubs from Phase 1)
52
+ 6. Verify file compiles: tsc --noEmit on the single file
53
+ 7. Checkpoint: .aegis/state.json `pages_built: [<slug>]` updated
54
+ ```
55
+
56
+ Repeat for every page. Use TaskCreate per page to track progress; mark each page completed as soon as the file compiles.
57
+
58
+ ---
59
+
60
+ ## Component-File Template (page-level)
61
+
62
+ For a page-level (custom) component, the canonical shape:
63
+
64
+ ```tsx
65
+ // app/<slug>/components/UniqueSection.tsx
66
+ 'use client'; // omit if pure RSC
67
+
68
+ import { cn } from '@/lib/utils';
69
+ import type { ComponentProps } from 'react';
70
+
71
+ interface Props extends ComponentProps<'section'> {
72
+ headline: string;
73
+ subline?: string;
74
+ // ... other props per briefing
75
+ }
76
+
77
+ export function UniqueSection({ headline, subline, className, ...rest }: Props) {
78
+ return (
79
+ <section className={cn('py-16 md:py-24', className)} {...rest}>
80
+ <div className="container mx-auto max-w-7xl px-4">
81
+ <h2 className="text-3xl font-bold md:text-5xl">{headline}</h2>
82
+ {subline && <p className="mt-4 text-lg text-muted-foreground">{subline}</p>}
83
+ {/* ... */}
84
+ </div>
85
+ </section>
86
+ );
87
+ }
88
+ ```
89
+
90
+ **Discipline:**
91
+ - Always typed props (no `any`).
92
+ - Always `cn()` for className composition.
93
+ - Always responsive (`md:`, `lg:` breakpoints).
94
+ - Always semantic HTML (`section`, `article`, `nav`, `header`, `footer`).
95
+ - Never mutate the imported library-component's internals — extend by composition, not inheritance.
96
+
97
+ ---
98
+
99
+ ## Common Library-Component Shapes
100
+
101
+ The project's component-library typically exposes these well-known shapes. The actual implementation depends on the bound library; here's the **conceptual contract** the customer-build expects:
102
+
103
+ ```tsx
104
+ // Hero
105
+ <HeroBlock
106
+ variant="image-left" | "image-right" | "centered" | "split"
107
+ headline={string}
108
+ subline={string}
109
+ primaryCta={{ label, href }}
110
+ secondaryCta?={{ label, href }}
111
+ imageSrc={string}
112
+ imageAlt={string}
113
+ />
114
+
115
+ // FeatureGrid
116
+ <FeatureGrid
117
+ variant="3-col" | "2-col" | "asymmetric"
118
+ features={Array<{ icon, title, description }>}
119
+ />
120
+
121
+ // Testimonials
122
+ <Testimonials
123
+ variant="carousel" | "grid" | "single"
124
+ items={Array<{ quote, name, role, avatarSrc? }>}
125
+ />
126
+
127
+ // PricingTable
128
+ <PricingTable
129
+ tiers={Array<{ name, price, description, features[], cta }>}
130
+ highlightTier?={string}
131
+ />
132
+
133
+ // CtaBanner
134
+ <CtaBanner
135
+ headline={string}
136
+ subline?={string}
137
+ cta={{ label, href }}
138
+ />
139
+ ```
140
+
141
+ If the bound library uses different prop-names — read the library's component-inventory in pre-build, then map briefing-shape to library-shape per a `references/library-mapping.md` (extension-point).
142
+
143
+ ---
144
+
145
+ ## Page-Level Composition Example
146
+
147
+ ```tsx
148
+ // app/page.tsx (home page)
149
+ import { HeroBlock, FeatureGrid, Testimonials, CtaBanner } from '@/components/library';
150
+ import { OriginStory } from './components/OriginStory';
151
+
152
+ export const metadata = {
153
+ title: '...',
154
+ description: '...',
155
+ };
156
+
157
+ export default function HomePage() {
158
+ return (
159
+ <>
160
+ <HeroBlock
161
+ variant="image-right"
162
+ headline="..."
163
+ subline="..."
164
+ primaryCta={{ label: 'Jetzt anfragen', href: '/kontakt' }}
165
+ imageSrc="/images/hero.webp"
166
+ imageAlt="..."
167
+ />
168
+ <FeatureGrid
169
+ variant="3-col"
170
+ features={[/* per briefing */]}
171
+ />
172
+ <OriginStory />
173
+ <Testimonials variant="carousel" items={[/* per briefing */]} />
174
+ <CtaBanner
175
+ headline="Bereit für den nächsten Schritt?"
176
+ cta={{ label: 'Kontakt aufnehmen', href: '/kontakt' }}
177
+ />
178
+ </>
179
+ );
180
+ }
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Quality-Gate Per Page
186
+
187
+ Before marking a page complete:
188
+
189
+ - [ ] File compiles (`tsc --noEmit` clean for that page's tree)
190
+ - [ ] Page renders without runtime errors (smoke-test via dev-server: `curl -s localhost:3000/<slug> | head -50` returns HTML, not 500)
191
+ - [ ] All briefing.sections[] are present in the JSX (count match)
192
+ - [ ] All required props are filled (no `headline=""` placeholders left)
193
+ - [ ] Image-paths point to actual files (not 404s) — placeholders for Phase 4 are OK if explicitly marked
194
+
195
+ If any unmet → page is incomplete; checkpoint stays `pages_built: [...without this page]`.
196
+
197
+ ---
198
+
199
+ ## Subagent-Dispatch Pattern
200
+
201
+ For builds with > 5 pages, dispatch parallel Executor-subagents:
202
+
203
+ ```
204
+ Master-agent:
205
+ for each batch in chunked(pages, 3):
206
+ dispatch Executor-subagent with prompt:
207
+ "Build pages [a, b, c] per architecture.md + briefing-parsed.json.
208
+ Library-mapping: <map>. Output: 3 page.tsx files.
209
+ Verify: tsc + curl smoke-test. Return checkpoint."
210
+
211
+ await all Executor-subagents
212
+ aggregate checkpoints into .aegis/state.json
213
+ ```
214
+
215
+ Subagent-prompt MUST include:
216
+ - The architecture.md path
217
+ - The briefing-parsed.json path
218
+ - The component-library mapping
219
+ - The verification-checklist (tsc + curl + sections-count)
220
+
221
+ Don't dispatch without these — subagents shouldn't have to rediscover them from chat-context.
222
+
223
+ ---
224
+
225
+ ## Anti-Patterns specific to Phase 3
226
+
227
+ - ❌ Hand-cobbling a section that has a library-component for it — drift from quality-bar.
228
+ - ❌ Skipping the per-page tsc + curl smoke-test "because the page looks fine" — page-renders-without-runtime-errors is gate-critical.
229
+ - ❌ Marking pages_built[<slug>] without actually verifying — checkpoints are honest, not optimistic.
230
+ - ❌ Building all pages in one Master-agent monologue when parallel Executor-subagents are available — wastes wall-clock time.
231
+ - ❌ Forgetting to copy library-component CSS / dependencies — verify the imports compile + the runtime CSS bundle includes the components.
@@ -0,0 +1,196 @@
1
+ # Phase 4 Reference — Content (Copy + Images + SEO)
2
+
3
+ Phase 4 fills the page-shells from Phase 3 with real content: copy in the briefing's tone, images placed per the design-prefs, SEO-meta + Open-Graph + Schema.org structured data. **Time budget:** 30-45 min.
4
+
5
+ ---
6
+
7
+ ## Copy-Writing — Tone-Match per Page
8
+
9
+ For each page, follow the brand-essence captured in `briefing-parsed.json.brand_identity`:
10
+
11
+ ```
12
+ Essence: <target_audience> looking for <value_proposition>; differentiated by <differentiators>, speaking in a <tone> voice with <voice>.
13
+ ```
14
+
15
+ **Example tone-matrices:**
16
+
17
+ | Tone | Voice | Lexical pattern |
18
+ |---|---|---|
19
+ | Vertraut, professionell | Sie + Plural-Wir | "Wir liefern Ihnen..." |
20
+ | Locker, modern | Du | "Du bekommst..." |
21
+ | Premium, gehoben | Sie + reduzierte Adjektive | "Unsere Manufaktur..." |
22
+ | Tech, präzise | Wir + Fachbegriffe | "Unsere Engine berechnet..." |
23
+
24
+ **Hard rules:**
25
+ - Don't mix Sie / Du within one site. Pick one per the briefing.
26
+ - Avoid Anglizisms unless the briefing uses them. "kostenlos" not "free", "Anmeldung" not "Sign-up".
27
+ - Avoid Marketing-Phrasen-Müll ("revolutionär", "einzigartig", "innovativ" without a concrete claim) — concrete > superlative.
28
+ - Avoid passive-voice when active works ("Wir bauen" > "Es wird gebaut").
29
+
30
+ ---
31
+
32
+ ## Per-Section Copy-Slot Workflow
33
+
34
+ For each page section that needs copy:
35
+
36
+ ```
37
+ 1. Read briefing-parsed.json.pages[<slug>].sections[<section>] for any provided copy
38
+ 2. If briefing has copy: use it verbatim
39
+ 3. If briefing has bullets but no prose: expand bullets into prose matching brand-essence
40
+ 4. If briefing has only intent ("hero CTA pointing at /kontakt"): generate per intent + tone-match
41
+ 5. Write copy into the JSX — no placeholder strings left
42
+ ```
43
+
44
+ **LLM-assist pattern** (for prose generation):
45
+
46
+ ```
47
+ System: <brand-essence-paragraph>
48
+ User: Generate hero subline for the home page.
49
+ Constraints: 1-2 sentences, ≤ 160 characters, mentions <key-differentiator>, ends with action-verb.
50
+ ```
51
+
52
+ Always operator-review LLM-generated copy. The briefing is the contract; LLM-fill is a placeholder until operator confirms in mid-audit.
53
+
54
+ ---
55
+
56
+ ## Image-Placement Workflow
57
+
58
+ The image-pipeline follows the operator's existing convention:
59
+
60
+ ```
61
+ 1. Read briefing.assets[] for image-references
62
+ 2. For each referenced image:
63
+ a. If a local file exists at briefing.assets[*].src: copy to public/images/<slug>-<section>.webp
64
+ b. If a placeholder is needed: generate via the operator's image-pipeline (e.g., Midjourney + cwebp-conversion to WebP)
65
+ c. Always WebP at quality 88 (cwebp -q 88) — saves ~40% bytes vs PNG/JPG
66
+ d. Always responsive sources at 1920w + 1280w + 640w breakpoints
67
+ 3. Update JSX: <Image src="/images/<file>.webp" alt="<alt-text>" width={...} height={...} quality={95} />
68
+ ```
69
+
70
+ **Hard rules:**
71
+ - Always `quality={95}` prop on `next/image` — default 75 re-compresses operator-uploaded high-quality images poorly.
72
+ - Always WebP for raster (PNG/JPG only for legacy logos that need transparency in browsers without WebP fallback — rare).
73
+ - Always alt-text — write meaningful alt-text per image-content, not "image.webp" or empty.
74
+
75
+ ---
76
+
77
+ ## Alt-Text Generation
78
+
79
+ For each image, alt-text MUST:
80
+
81
+ 1. Describe what's in the image (factual, concrete).
82
+ 2. Be ≤ 120 characters.
83
+ 3. Not start with "Image of..." or "Picture showing..." — redundant; screenreaders announce "image" already.
84
+ 4. Match the image-context (e.g., for a hero-image, the alt-text relates to the hero-headline).
85
+ 5. Be unique across the page (no two images with identical alt-text).
86
+
87
+ **Example:**
88
+
89
+ - ❌ "image.webp"
90
+ - ❌ "Picture of a person"
91
+ - ✅ "Frau am Laptop, fokussiert beim Coden im hellen Atelier"
92
+
93
+ ---
94
+
95
+ ## SEO-Meta per Page
96
+
97
+ Every page exports `metadata` (App Router) or sets `<head>` (Pages Router) with:
98
+
99
+ ```tsx
100
+ export const metadata: Metadata = {
101
+ title: '<unique-per-page>', // 50-60 chars, includes brand at end
102
+ description: '<unique-per-page>', // 120-160 chars, includes target_keyword
103
+ alternates: {
104
+ canonical: '/<slug>',
105
+ },
106
+ openGraph: {
107
+ title: '<og-title>',
108
+ description: '<og-description>',
109
+ images: ['/images/og-<slug>.webp'],
110
+ type: 'website',
111
+ },
112
+ twitter: {
113
+ card: 'summary_large_image',
114
+ title: '<twitter-title>',
115
+ description: '<twitter-description>',
116
+ images: ['/images/og-<slug>.webp'],
117
+ },
118
+ };
119
+ ```
120
+
121
+ **Per-page-uniqueness check:** every page's `title` is unique (no duplicates). Same for `description`. Phase 7 briefing-coverage gate verifies this.
122
+
123
+ ---
124
+
125
+ ## Schema.org Structured Data
126
+
127
+ Inject JSON-LD per page-type:
128
+
129
+ | Page-type | Schema |
130
+ |---|---|
131
+ | Home (organization) | `Organization` + `LocalBusiness` (if local biz) |
132
+ | Service-page | `Service` |
133
+ | Blog-post | `BlogPosting` |
134
+ | FAQ-page | `FAQPage` |
135
+ | Product | `Product` |
136
+ | Contact | `ContactPage` |
137
+ | About | `AboutPage` |
138
+
139
+ Render via a `JsonLdScript` component:
140
+
141
+ ```tsx
142
+ <JsonLdScript
143
+ schema={{
144
+ '@context': 'https://schema.org',
145
+ '@type': 'Organization',
146
+ name: '<brand>',
147
+ url: '<canonical-url>',
148
+ logo: '<logo-url>',
149
+ sameAs: [/* social-links from briefing */],
150
+ }}
151
+ />
152
+ ```
153
+
154
+ Use a Next.js Script component (strategy="beforeInteractive") to ensure Google sees the schema before user-interaction.
155
+
156
+ ---
157
+
158
+ ## Open-Graph Images
159
+
160
+ Generate one OG-image per page (or share a default for sub-pages):
161
+
162
+ ```
163
+ 1. Use a generator (e.g., next/og or Vercel OG) at /api/og?page=<slug>
164
+ 2. Or pre-generate static .webp/.png at public/images/og-<slug>.webp
165
+ 3. 1200x630 for Facebook/LinkedIn; 1200x675 for Twitter (use 1200x630 for both, slight crop OK)
166
+ ```
167
+
168
+ If briefing doesn't specify per-page OG-images, reuse the home-page OG for sub-pages.
169
+
170
+ ---
171
+
172
+ ## Content-Completion Checklist
173
+
174
+ Before marking Phase 4 complete:
175
+
176
+ - [ ] Every page has copy in every section (no `Lorem ipsum` or `<placeholder>` left)
177
+ - [ ] Every image is placed (no `<img src="">` or 404 paths)
178
+ - [ ] Every image has meaningful alt-text
179
+ - [ ] Every page has unique SEO-meta title + description
180
+ - [ ] Every page has canonical URL set
181
+ - [ ] Every page has OG + Twitter card meta
182
+ - [ ] Every applicable page-type has Schema.org JSON-LD
183
+ - [ ] No anglicisms / marketing-müll / placeholder-prose
184
+
185
+ If any unmet → Phase 4 is incomplete. List failing items in `.aegis/state.json.phase_4_open[]`.
186
+
187
+ ---
188
+
189
+ ## Anti-Patterns specific to Phase 4
190
+
191
+ - ❌ Leaving "Lorem ipsum" or "<placeholder>" strings — they survive into production. Grep before commit.
192
+ - ❌ Using `<Image quality={75}>` (default) — re-compresses high-quality uploads poorly.
193
+ - ❌ Same SEO-title across pages — Google will deduplicate and one page wins.
194
+ - ❌ Empty or duplicate alt-texts — fails A11y.
195
+ - ❌ Schema.org JSON-LD with placeholder URLs — Google flags "Site doesn't match schema."
196
+ - ❌ Mixing Sie/Du within one page — operator-confirms one or the other in Phase 1.
@@ -0,0 +1,273 @@
1
+ # Phase 5 Reference — Integration (API-Routes + Forms + Chatbot + Scanner)
2
+
3
+ Phase 5 wires up the dynamic backends. Outputs: API-routes under `app/api/`, form-submission handlers, chatbot mount in `app/layout.tsx`, scanner mount under `app/scan/`. **Time budget:** 30-45 min.
4
+
5
+ ---
6
+
7
+ ## API-Route Template (canonical)
8
+
9
+ Every API-route uses the `secureApiRoute` wrapper. Canonical shape:
10
+
11
+ ```ts
12
+ // app/api/<endpoint>/route.ts
13
+ import { secureApiRoute } from '@/lib/api/secure-route';
14
+ import { z } from 'zod';
15
+
16
+ const requestSchema = z.object({
17
+ name: z.string().min(1).max(100),
18
+ email: z.string().email().max(254),
19
+ message: z.string().min(10).max(5000),
20
+ });
21
+
22
+ export const POST = secureApiRoute({
23
+ schema: requestSchema,
24
+ rateLimit: { perIpPerHour: 30 },
25
+ origins: ['https://<production-domain>', 'http://localhost:3000'],
26
+ handler: async ({ body, req }) => {
27
+ // body is typed + validated per requestSchema
28
+ const result = await processSubmission(body);
29
+ return Response.json({ success: true, id: result.id });
30
+ },
31
+ });
32
+ ```
33
+
34
+ **`secureApiRoute` wraps:**
35
+
36
+ 1. Origin-check (allow-list per `origins[]`; rejects cross-origin POST).
37
+ 2. Rate-limit (Redis-backed or in-memory per IP).
38
+ 3. Body-parse + Zod validation (returns 400 on validation-fail).
39
+ 4. Honeypot field check (rejects if `_honey` field non-empty).
40
+ 5. Handler invocation (passes typed body + raw req).
41
+ 6. Error-mapping (maps thrown errors to clean JSON responses).
42
+
43
+ The wrapper itself lives in `lib/api/secure-route.ts` — copy from foundation template.
44
+
45
+ ---
46
+
47
+ ## Form-Pattern (Contact Form)
48
+
49
+ Canonical contact-form with double-opt-in:
50
+
51
+ ```tsx
52
+ // components/forms/ContactForm.tsx
53
+ 'use client';
54
+
55
+ import { useForm } from 'react-hook-form';
56
+ import { zodResolver } from '@hookform/resolvers/zod';
57
+ import { contactSchema } from '@/lib/schemas/contact';
58
+
59
+ export function ContactForm() {
60
+ const { register, handleSubmit, formState } = useForm({
61
+ resolver: zodResolver(contactSchema),
62
+ });
63
+
64
+ const onSubmit = async (data) => {
65
+ const res = await fetch('/api/contact', {
66
+ method: 'POST',
67
+ headers: { 'Content-Type': 'application/json' },
68
+ body: JSON.stringify(data),
69
+ });
70
+ if (!res.ok) throw new Error(await res.text());
71
+ // Show "We sent you a confirmation email" UI
72
+ };
73
+
74
+ return (
75
+ <form onSubmit={handleSubmit(onSubmit)} noValidate>
76
+ <input type="text" {...register('name')} aria-invalid={!!formState.errors.name} />
77
+ <input type="email" {...register('email')} />
78
+ <textarea {...register('message')} />
79
+ {/* Honeypot — bots fill, humans don't */}
80
+ <input type="text" name="_honey" style={{ position: 'absolute', left: '-9999px' }} tabIndex={-1} aria-hidden="true" />
81
+ {/* DSGVO-consent */}
82
+ <label>
83
+ <input type="checkbox" {...register('consent')} />
84
+ Ich willige in die Verarbeitung meiner Daten ein. <a href="/datenschutz">Datenschutz</a>
85
+ </label>
86
+ <button type="submit" disabled={formState.isSubmitting}>
87
+ {formState.isSubmitting ? 'Wird gesendet...' : 'Senden'}
88
+ </button>
89
+ </form>
90
+ );
91
+ }
92
+ ```
93
+
94
+ **DSGVO-Discipline:**
95
+
96
+ - Consent-checkbox required before submit (no pre-checked checkbox — must be explicit user-action).
97
+ - "Datenschutz" link visible next to consent-text.
98
+ - Server-side: store consent-timestamp + IP-hash for audit trail (Art. 7 Abs. 1 DSGVO).
99
+ - Double-opt-in: send confirmation email; don't store final submission until user clicks the email link.
100
+
101
+ ---
102
+
103
+ ## Chatbot Mount Pattern
104
+
105
+ If `briefing.integrations` includes `chatbot.public-llm`:
106
+
107
+ ```tsx
108
+ // app/layout.tsx
109
+ import { ChatWidget } from '@/components/chat/ChatWidget';
110
+
111
+ export default function RootLayout({ children }) {
112
+ return (
113
+ <html lang="de">
114
+ <body>
115
+ {children}
116
+ <ChatWidget endpoint="/api/chat" persona={chatPersona} />
117
+ </body>
118
+ </html>
119
+ );
120
+ }
121
+ ```
122
+
123
+ ```ts
124
+ // app/api/chat/route.ts
125
+ import { secureApiRoute } from '@/lib/api/secure-route';
126
+ import { z } from 'zod';
127
+ import { chatPersona } from '@/lib/chat/persona';
128
+
129
+ const messageSchema = z.object({
130
+ message: z.string().min(1).max(2000),
131
+ history: z.array(z.object({ role: z.enum(['user', 'assistant']), content: z.string() })).max(10),
132
+ });
133
+
134
+ export const POST = secureApiRoute({
135
+ schema: messageSchema,
136
+ rateLimit: { perIpPerHour: 60 },
137
+ handler: async ({ body }) => {
138
+ // Sanitize user input (anti-prompt-injection)
139
+ const sanitized = sanitizeInput(body.message);
140
+ // Redact PII before sending to LLM
141
+ const redacted = redactPii(sanitized);
142
+ // Stream response via SSE
143
+ const stream = await callLlm({
144
+ systemPrompt: chatPersona.systemPrompt,
145
+ messages: [...body.history, { role: 'user', content: redacted }],
146
+ });
147
+ return new Response(stream, {
148
+ headers: { 'Content-Type': 'text/event-stream' },
149
+ });
150
+ },
151
+ });
152
+ ```
153
+
154
+ **Anti-Injection-Defense-Layer:**
155
+
156
+ - Sanitize user input (strip suspicious patterns: `system:`, `assistant:`, `<|im_start|>`, base64-blobs > 100 chars).
157
+ - PII-redact before LLM-call (regex + named-entity).
158
+ - Persona system-prompt with explicit "stay in role" rules.
159
+ - Output-filter (strip any "I cannot..." that leaks training-data; strip markdown-headers from responses).
160
+ - Rate-limit + per-IP cooldown.
161
+
162
+ The detailed defense-layer-list is in `compliance/aegis-native/brutaler-anwalt/references/audit-patterns.md` (cross-skill reference).
163
+
164
+ ---
165
+
166
+ ## Scanner Mount Pattern
167
+
168
+ If `briefing.integrations` includes `scanner.aegis`:
169
+
170
+ ```tsx
171
+ // app/scan/page.tsx
172
+ import { ScannerWidget } from '@aegis-scan/react';
173
+
174
+ export default function ScanPage() {
175
+ return (
176
+ <main>
177
+ <h1>Site-Compliance-Check</h1>
178
+ <ScannerWidget />
179
+ </main>
180
+ );
181
+ }
182
+ ```
183
+
184
+ ```ts
185
+ // app/api/scan/route.ts
186
+ import { secureApiRoute } from '@/lib/api/secure-route';
187
+ import { runScan } from '@aegis-scan/runner';
188
+ import { z } from 'zod';
189
+
190
+ const scanSchema = z.object({
191
+ url: z.string().url(),
192
+ depth: z.enum(['quick', 'full']).default('quick'),
193
+ });
194
+
195
+ export const POST = secureApiRoute({
196
+ schema: scanSchema,
197
+ rateLimit: { perIpPerHour: 5 },
198
+ handler: async ({ body }) => {
199
+ const result = await runScan(body.url, { depth: body.depth });
200
+ return Response.json(result);
201
+ },
202
+ });
203
+ ```
204
+
205
+ Scanner has tighter rate-limit (5/hr) — scans are expensive + can be abused for reconnaissance.
206
+
207
+ ---
208
+
209
+ ## Cookie-Banner Mount
210
+
211
+ If `briefing.legal.cookie_strategy != 'none'` (i.e., the site uses cookies that need consent):
212
+
213
+ ```tsx
214
+ // app/layout.tsx
215
+ import { CookieBanner } from '@/components/layout/CookieBanner';
216
+
217
+ <CookieBanner
218
+ strategy={briefing.legal.cookie_strategy} // 'granular' | 'global'
219
+ vendors={[/* per briefing.legal.vendors */]}
220
+ />
221
+ ```
222
+
223
+ **TTDSG/TDDDG §25 compliance:**
224
+
225
+ - No technically-non-required cookie set before user consent.
226
+ - Granular > global (per BGH). Each vendor gets its own opt-in checkbox.
227
+ - Banner has equal-prominence "Accept" / "Reject all" buttons (no dark-pattern of small "reject" link).
228
+ - Re-consent UI at `/cookies/einstellungen` (or footer-link).
229
+
230
+ Cookie-banner pattern detail belongs in `dsgvo-compliance/references/cookie-pattern.md` (cross-skill reference).
231
+
232
+ ---
233
+
234
+ ## Analytics Mount
235
+
236
+ If `briefing.integrations.analytics` is set:
237
+
238
+ | Provider | Consent-required | Mount |
239
+ |---|---|---|
240
+ | Plausible | No (anonymous, no cookies) | `<script defer src="https://plausible.io/js/script.js" />` in layout.tsx |
241
+ | Matomo (self-hosted) | Depends on config | Behind cookie-banner if cookies enabled |
242
+ | Google Analytics 4 | Yes | Behind cookie-banner; load only after consent |
243
+ | Fathom | No | Anonymous mode default; `<script async src="https://cdn.usefathom.com/script.js" />` |
244
+
245
+ Default to Plausible for new builds (privacy-friendly + DSGVO-clean by design).
246
+
247
+ ---
248
+
249
+ ## Integration-Completion Checklist
250
+
251
+ Before marking Phase 5 complete:
252
+
253
+ - [ ] Every API-route uses `secureApiRoute` wrapper (no raw `export const POST` without wrapper)
254
+ - [ ] Every form has DSGVO-consent + honeypot + double-opt-in flow
255
+ - [ ] Chatbot (if enabled) has anti-injection defense-layers active
256
+ - [ ] Scanner (if enabled) has tight rate-limit (5/hr)
257
+ - [ ] Cookie-banner (if needed) is granular per BGH + has equal-prominence accept/reject
258
+ - [ ] Analytics (if enabled) is consent-aware per provider
259
+ - [ ] All API-routes return JSON (no HTML / unstructured text)
260
+ - [ ] All API-routes have unit tests for happy + sad paths
261
+
262
+ ---
263
+
264
+ ## Anti-Patterns specific to Phase 5
265
+
266
+ - ❌ Skipping `secureApiRoute` wrapper "because the route is internal" — all routes get the wrapper.
267
+ - ❌ Forms without honeypot — bots will spam.
268
+ - ❌ Forms without double-opt-in — single-opt-in is no longer valid for newsletter under DSGVO + UWG.
269
+ - ❌ Chatbot without anti-injection-layer — first jailbreak attempt will leak.
270
+ - ❌ Cookie-banner with pre-checked checkboxes — illegal under TTDSG §25.
271
+ - ❌ Cookie-banner with prominent "Accept" + tiny "Reject" link — dark-pattern, BGH explicitly disallows.
272
+ - ❌ Loading Google Analytics before consent — DSGVO violation.
273
+ - ❌ Pre-loading any tracker before consent — TTDSG §25 violation.