srcdev-nuxt-components 9.1.5 → 9.1.7

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.
@@ -21,7 +21,8 @@
21
21
  "additionalDirectories": [
22
22
  "/Users/simoncornforth/websites/nuxt-components/app/components/01.atoms/content-wrappers/content-width",
23
23
  "/Users/simoncornforth/websites/nuxt-components/.claude/skills",
24
- "/Users/simoncornforth/websites/nuxt-components/app/components/02.molecules/navigation/site-navigation/tests"
24
+ "/Users/simoncornforth/websites/nuxt-components/app/components/02.molecules/navigation/site-navigation/tests",
25
+ "/Users/simoncornforth/websites/luxury-locs-by-natasha-nuxt3/app/pages"
25
26
  ]
26
27
  }
27
28
  }
@@ -6,21 +6,31 @@
6
6
 
7
7
  ## Props
8
8
 
9
- | Prop | Type | Default | Required |
10
- | ----------------------- | --------------------------------- | -------- | -------- |
11
- | `servicesData` | `Service[]` | — | **yes** |
12
- | `tag` | `"div" \| "section" \| "main"` | `"div"` | no |
13
- | `eyebrowConfig` | `EyebrowConfig` | `{}` | no |
14
- | `heroConfig` | `HeroConfig` | `{}` | no |
15
- | `styleClassPassthrough` | `string \| string[]` | `[]` | no |
9
+ | Prop | Type | Default | Required |
10
+ | ----------------------- | --------------------------------- | -------------------------------- | -------- |
11
+ | `servicesData` | `Service[]` | — | **yes** |
12
+ | `tag` | `"div" \| "section" \| "main"` | `"div"` | no |
13
+ | `eyebrowConfig` | `EyebrowConfig` | `{}` | no |
14
+ | `heroConfig` | `HeroConfig` | `{}` | no |
15
+ | `hrefBase` | `string` | `"/ui/services/services-section/"` | no |
16
+ | `buttonTextPrefix` | `string` | `"Enquire about"` | no |
17
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | no |
16
18
 
17
19
  Both config props are passed through to every `ServicesCard` in the grid unchanged. See [services-card.md](./services-card.md) for the full `EyebrowConfig` / `HeroConfig` key reference.
18
20
 
19
- ## Slots
21
+ ### Button link and text
20
22
 
21
- | Slot | Slot props | Purpose |
22
- | --------- | -------------------------- | ------------------------------------------------ |
23
- | `actions` | `{ serviceData: Service }` | CTA rendered inside each card below description |
23
+ Each card's CTA button is built internally as `${buttonTextPrefix} ${service.title}` linking to `${hrefBase}${service.slug}`. Override both at the call site:
24
+
25
+ ```vue
26
+ <ServicesCardGrid
27
+ :services-data="servicesData ?? []"
28
+ href-base="/services/"
29
+ button-text-prefix="More on"
30
+ />
31
+ ```
32
+
33
+ > **Note**: `ServicesCardGrid` does **not** forward a consumer `#actions` slot to its internal `ServicesCard` instances. Use `hrefBase` / `buttonTextPrefix` props to customise the CTA.
24
34
 
25
35
  ## CSS custom properties
26
36
 
@@ -47,20 +57,9 @@ Set on `.services-card-grid` (or scoped to a page class):
47
57
  :services-data="servicesData ?? []"
48
58
  :eyebrow-config="{ fontSize: 'large' }"
49
59
  :hero-config="{ tag: 'h2', fontSize: 'heading' }"
50
- >
51
- <template #actions="{ serviceData }">
52
- <InputButtonCore
53
- variant="secondary"
54
- :button-text="`Enquire about ${serviceData.title}`"
55
- :href="`/services/${serviceData.slug}`"
56
- :style-class-passthrough="['mbs-24']"
57
- >
58
- <template #right>
59
- <Icon name="mdi:arrow-right" class="icon" />
60
- </template>
61
- </InputButtonCore>
62
- </template>
63
- </ServicesCardGrid>
60
+ href-base="/services/"
61
+ button-text-prefix="More on"
62
+ />
64
63
  </LayoutRow>
65
64
  </template>
66
65
  </NuxtLayout>
@@ -20,6 +20,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
20
20
  .claude/skills/
21
21
  ├── index.md — this file
22
22
  ├── performance-review.md — spawn a subagent to inspect recently written code for Vue/Nuxt performance issues before dev handoff
23
+ ├── security-review.md — spawn a subagent to inspect recently written code for security vulnerabilities (XSS, injection, data exposure, etc.) before dev handoff
23
24
  ├── storybook-add-story.md — create a Storybook story for a component
24
25
  ├── storybook-add-font.md — add a new font to Storybook
25
26
  ├── testing-add-unit-test.md — create a Vitest unit test with snapshots
@@ -0,0 +1,151 @@
1
+ # Security Review
2
+
3
+ ## Overview
4
+
5
+ Spawn a subagent to inspect recently written or modified code for security issues and report findings back before handoff to dev. Run this after code is written and ready for review.
6
+
7
+ ## When to invoke
8
+
9
+ Invoke this skill (`/security-review`) after completing any of:
10
+ - A new component that accepts user input or renders external data
11
+ - A significant edit to an existing component
12
+ - A new composable that handles URLs, tokens, or external data
13
+ - Any code that constructs URLs, HTML, or interacts with browser APIs
14
+
15
+ ## Steps
16
+
17
+ ### 1. Identify files to review
18
+
19
+ Collect the list of files changed in this conversation. If unclear, run `git diff --name-only HEAD` to get recently modified files.
20
+
21
+ ### 2. Spawn the security review subagent
22
+
23
+ Use the Agent tool with `subagent_type: "general-purpose"` and the following prompt, substituting `{FILE_LIST}` with the actual file paths:
24
+
25
+ ---
26
+
27
+ **Subagent prompt template:**
28
+
29
+ ```
30
+ You are a Vue 3 / Nuxt security reviewer. Inspect the following files for security vulnerabilities and produce a concise report.
31
+
32
+ Files to review:
33
+ {FILE_LIST}
34
+
35
+ Read each file in full, then check for the following issues. Report only issues that are actually present — do not flag hypothetical problems.
36
+
37
+ ─── XSS / Injection ─────────────────────────────────────────────
38
+ □ `v-html` used with any non-static string (potential XSS — must be sanitised before use)
39
+ □ Template literals passed to `v-html` or `innerHTML`
40
+ □ User-controlled data rendered without escaping
41
+ □ `eval()`, `new Function()`, or `setTimeout(string)` usage
42
+
43
+ ─── URL / Redirect Safety ───────────────────────────────────────
44
+ □ `href` or `src` built from user-supplied data without validation (open redirect / javascript: injection)
45
+ □ Dynamic `<script src>` or `<link href>` construction
46
+ □ `window.location` assignment from unvalidated input
47
+
48
+ ─── Data Exposure ───────────────────────────────────────────────
49
+ □ Secrets, API keys, or tokens hardcoded or logged to console
50
+ □ Sensitive data stored in `localStorage` / `sessionStorage` without encryption
51
+ □ `useRuntimeConfig()` public keys that expose values that should be private (server-only)
52
+ □ Props or emits that inadvertently expose internal state to the DOM (e.g. `data-*` attributes)
53
+
54
+ ─── Component / Props ───────────────────────────────────────────
55
+ □ `inheritAttrs: true` on components that accept `href` / `src` — an attacker can pass arbitrary attributes through
56
+ □ Props that accept raw HTML strings without a sanitisation step
57
+ □ Dynamic `is` prop on `<component>` bound to user-controlled data (component injection)
58
+
59
+ ─── Event Handling ──────────────────────────────────────────────
60
+ □ `@click.native` or direct DOM event listeners on user-supplied elements
61
+ □ `postMessage` handlers without origin validation
62
+ □ Clipboard API writes of unvalidated data
63
+
64
+ ─── Dependencies / Imports ──────────────────────────────────────
65
+ □ Dynamic `import()` paths constructed from user input
66
+ □ Imports from non-registry URLs (e.g. raw GitHub, CDN) rather than npm packages
67
+
68
+ ─── Broken Access Control (OWASP A01) ───────────────────────────
69
+ □ `v-if` used as the sole gate for sensitive UI — this is cosmetic only, not a real access check
70
+ □ Route middleware (`definePageMeta({ middleware })`) that can be bypassed by navigating directly
71
+ □ Server routes or API handlers that trust `event.context.auth` / headers set by the client
72
+ □ Permissions or roles derived solely from client-side reactive state with no server validation
73
+
74
+ ─── Cryptographic Failures (OWASP A02) ──────────────────────────
75
+ □ `Math.random()` used to generate tokens, IDs, or nonces (not cryptographically secure — use `crypto.randomUUID()` or `crypto.getRandomValues()`)
76
+ □ `btoa()` / `atob()` used to "encode" sensitive values (this is encoding, not encryption)
77
+ □ Sensitive values hashed with MD5 or SHA-1 (use SHA-256+ or bcrypt/argon2 for passwords)
78
+ □ Passwords or secrets stored in plain text in state, localStorage, or cookies
79
+
80
+ ─── Security Misconfiguration (OWASP A05) ───────────────────────
81
+ □ `nuxt.config.ts` missing security headers (`Content-Security-Policy`, `X-Frame-Options`, `Referrer-Policy`)
82
+ □ CORS configured with `origin: "*"` on routes that return sensitive data
83
+ □ Verbose error messages or stack traces returned to the client from server routes
84
+ □ `devtools: true` or debug flags that could be active in production builds
85
+ □ `runtimeConfig` values that mix public/private — anything in `public:` is exposed to the client bundle
86
+
87
+ ─── Authentication Failures (OWASP A07) ─────────────────────────
88
+ □ Auth tokens stored in `localStorage` or `sessionStorage` (vulnerable to XSS — prefer `httpOnly` cookies)
89
+ □ Auth state held only in client-side reactive state (`useState`, `ref`) with no server-side verification
90
+ □ JWTs decoded client-side without signature verification before trusting claims
91
+ □ Session IDs or tokens exposed in URL query parameters or `data-*` attributes
92
+ □ No expiry enforced on tokens or session state
93
+
94
+ ─── Software & Data Integrity (OWASP A08) ───────────────────────
95
+ □ `JSON.parse()` called on untrusted/external data without a validation step (schema check or try/catch with type guard)
96
+ □ `Object.assign({}, userInput)` or `{ ...userInput }` spread onto internal objects — risk of prototype pollution if input is not sanitised
97
+ □ External `<script>` tags added via `useHead` without a `crossorigin` and `integrity` (SRI) attribute
98
+ □ Dynamic `import()` resolved from user-supplied strings (supply chain / path traversal risk)
99
+
100
+ ─── Logging & Monitoring (OWASP A09) ────────────────────────────
101
+ □ `console.log` / `console.error` statements that output tokens, passwords, or PII
102
+ □ Error handlers that re-throw raw server errors (including DB messages) to the client response
103
+ □ Caught exceptions silently swallowed with no logging — makes incidents invisible
104
+
105
+ ─── Server-Side Request Forgery (OWASP A10) ─────────────────────
106
+ □ `$fetch` / `useFetch` / `ofetch` called server-side with a URL constructed from user input (SSRF — attacker can target internal services)
107
+ □ Proxy or redirect endpoints that forward to a caller-supplied URL without an allowlist check
108
+ □ `useAsyncData` key derived from user input used to construct a server-side fetch URL
109
+
110
+ ─── CSRF ────────────────────────────────────────────────────────
111
+ □ State-mutating server routes (POST/PUT/DELETE) that rely solely on cookies without a CSRF token or `SameSite=Strict`
112
+ □ Forms that submit to API routes with no CSRF protection and no `Content-Type: application/json` enforcement (which prevents simple-form cross-origin attacks)
113
+
114
+ ─── Nuxt-specific ───────────────────────────────────────────────
115
+ □ Server routes or API handlers that trust client-sent data without validation
116
+ □ `useFetch` / `$fetch` URLs built from user input without sanitisation
117
+ □ `definePageMeta` `middleware` bypassed by conditional rendering
118
+ □ SSR hydration mismatches caused by client-only globals accessed during render
119
+
120
+ ─── Report format ───────────────────────────────────────────────
121
+ For each issue found, output:
122
+
123
+ **File:** `path/to/file.vue` (line N)
124
+ **Issue:** One-sentence description of the vulnerability
125
+ **Severity:** Low / Medium / High / Critical
126
+ **Fix:** Concrete code change or approach to resolve it
127
+
128
+ Group findings by file. If no issues are found in a file, skip it.
129
+ End the report with a one-line summary: total issues found, breakdown by severity.
130
+ ```
131
+
132
+ ---
133
+
134
+ ### 3. Review the report
135
+
136
+ Read the subagent's findings. For each **Critical** or **High** severity issue:
137
+ - Apply the fix immediately if it is clear-cut and does not change behaviour
138
+ - Flag it to the user with the suggested fix if it requires a design decision
139
+
140
+ For **Medium** and **Low** severity issues, present them as a list for the user to decide on.
141
+
142
+ ### 4. Confirm completion
143
+
144
+ After applying any fixes, re-run the subagent on the modified files to confirm no new issues were introduced.
145
+
146
+ ## Notes
147
+
148
+ - This skill is read-only when used via the subagent — it does not edit files directly. Edits are made by the main agent after reviewing the report.
149
+ - Report only issues that are actually present in the code — do not flag theoretical future vulnerabilities.
150
+ - The subagent uses `subagent_type: "general-purpose"` so it has access to all tools (Read, Grep, Glob) needed to inspect the files.
151
+ - Run alongside `/performance-review` — both can be spawned in parallel as independent subagents.
@@ -10,8 +10,8 @@
10
10
  <template #actions="{ serviceData }">
11
11
  <InputButtonCore
12
12
  variant="secondary"
13
- :button-text="`Enquire about ${serviceData.title}`"
14
- :href="`/ui/services/services-section/${serviceData.slug}`"
13
+ :button-text="`${buttonTextPrefix} ${serviceData.title}`"
14
+ :href="`${hrefBase}${serviceData.slug}`"
15
15
  :style-class-passthrough="['mbs-24']"
16
16
  >
17
17
  <template #right>
@@ -41,6 +41,8 @@ interface Props {
41
41
  servicesData: Service[];
42
42
  eyebrowConfig?: EyebrowConfig;
43
43
  heroConfig?: HeroConfig;
44
+ hrefBase?: string;
45
+ buttonTextPrefix?: string;
44
46
  styleClassPassthrough?: string | string[];
45
47
  }
46
48
 
@@ -48,6 +50,8 @@ const props = withDefaults(defineProps<Props>(), {
48
50
  tag: "div",
49
51
  eyebrowConfig: () => ({}),
50
52
  heroConfig: () => ({}),
53
+ hrefBase: "/ui/services/services-section/",
54
+ buttonTextPrefix: "Enquire about",
51
55
  styleClassPassthrough: () => [],
52
56
  });
53
57
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.1.5",
4
+ "version": "9.1.7",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",