Add design/accessibility/security/sanitization skills

This commit is contained in:
iWP Claudy
2026-08-02 20:15:46 +02:00
parent 39a637410c
commit 5af3379470
5 changed files with 329 additions and 0 deletions
+8
View File
@@ -43,6 +43,14 @@ than assuming the delegate can fetch it itself.
as fact; how to actually re-verify.
- `karpathy-guidelines` -- general LLM-coding behavioral guidelines
(simplicity, surgical changes, surfacing assumptions).
- `web-design-best-practices` -- modern UI/UX conventions to apply by
default (typography, spacing, color/contrast, motion, forms).
- `web-accessibility` -- WCAG-aligned baseline (semantic HTML, keyboard
nav, contrast, ARIA usage, forms).
- `security-headers-and-tls` -- HTTP security headers and TLS/SSL
configuration strength, including reverse-proxy-layer gotchas.
- `code-sanitization` -- framework-agnostic injection-prevention baseline
(SQLi, XSS, command injection, path traversal, uploads, deserialization).
## Provenance
+99
View File
@@ -0,0 +1,99 @@
---
name: code-sanitization
description: Use when writing any code that handles external input (user input, API responses, file uploads, database queries, shell commands). Framework-agnostic injection-prevention baseline.
---
# Code Sanitization
Framework-agnostic baseline for handling untrusted input safely. Applies
regardless of language — the specific function names differ, the
principle doesn't.
## The core rule: sanitize on input, escape on output, separately
These are two different concerns, both required:
- **Sanitize/validate on input**: reject or normalize data that doesn't
match the expected shape, as early as possible (at the point you first
receive it).
- **Escape on output**: transform data for the specific context it's
being placed into (HTML, a SQL query, a shell command, a URL, JSON) —
every different output context needs its OWN escaping, applied at the
point of output, not once globally.
A common real mistake: sanitizing once on input and assuming that's
sufficient for every later output context. It isn't — data that's safe
to store isn't automatically safe to interpolate into HTML, and separately
isn't automatically safe to interpolate into a shell command.
## SQL injection
**Parameterized queries / prepared statements, always, no exceptions.**
Never build a SQL string via concatenation or interpolation with a
variable in it, even if you're "sure" the variable is safe (a value that
was safe when the code was written is not guaranteed to stay safe as
the codebase evolves and new call sites appear).
## Cross-site scripting (XSS)
Escape for the exact context:
- HTML body text: HTML-entity-escape (`<`, `>`, `&`, `"`, `'`).
- HTML attribute: attribute-escape (stricter than body-text escaping).
- JavaScript string embedded in a `<script>` block: JS-string-escape (NOT
the same as HTML-escaping — a common real mistake).
- URL parameter: URL-encode.
Use the templating/framework's built-in auto-escaping wherever available
rather than hand-rolling escaping — auto-escaping systems have already
solved the context-detection problem correctly; hand-rolled escaping is
an easy place to get a context wrong.
## Command injection
Never build a shell command string by concatenating untrusted input. Use
an API that passes arguments as a real array/list (avoiding shell
interpretation entirely) rather than a single command string, wherever
the language/runtime offers that option. If a single command string is
genuinely unavoidable, every untrusted component needs proper shell-
escaping for the target shell — and even then, prefer the array-argument
API if it exists.
See the `remote-shell-quoting-safety` skill for the specific, separate
problem of content getting corrupted (not maliciously, just broken) when
passing through multiple nested shell layers — a different concern from
injection, but often encountered in the same code paths.
## Path traversal
Never build a filesystem path by directly concatenating user input.
Validate the resolved absolute path stays within the intended base
directory (resolve `..`/symlinks and check the result, don't just
regex-reject `..` in the raw input — that's bypassable). Prefer an
allowlist of permitted filenames/paths over trying to blocklist
dangerous patterns.
## File uploads
- Validate actual file content/type (magic bytes), not just the
extension or the client-supplied MIME type — both are trivially
spoofable.
- Store uploads outside the web-servable document root, or with
execution disabled for that directory, so an uploaded file can never
be directly requested and executed as code even if a validation gap
lets something malicious through.
- Enforce a size limit server-side, not just client-side.
## Deserialization
Never deserialize untrusted data with a format/library that can
instantiate arbitrary objects or execute code as a side effect of
deserializing (e.g. PHP's `unserialize()` on untrusted input, Python's
`pickle.loads()` on untrusted input). Use a safe, data-only format (JSON)
for anything touching untrusted input.
## General principle
When in doubt about whether input is "trusted," treat it as untrusted.
Data crossing any trust boundary (a network request, a file upload, a
database read of data that was itself written by a less-trusted
component, an environment variable in a multi-tenant context) should be
validated/escaped as if it were directly attacker-controlled, because in
a surprising number of real incidents, eventually it was.
+80
View File
@@ -0,0 +1,80 @@
---
name: security-headers-and-tls
description: Use when configuring or reviewing a web server/site's HTTP security headers and TLS/SSL setup. Baseline hardening to apply on any real site, not just when explicitly requested.
---
# Security Headers and TLS
## HTTP security headers (baseline for any real site)
```
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
Content-Security-Policy: <site-specific -- see below>
```
- **HSTS** (`Strict-Transport-Security`): only send this once HTTPS is
confirmed fully working site-wide (including all subdomains covered by
`includeSubDomains`) — sending it prematurely can lock out HTTP access
to a subdomain that isn't actually HTTPS-ready yet, and it's
effectively irreversible for the `max-age` duration for anyone who
already received it.
- **X-Frame-Options** (or the modern equivalent, `frame-ancestors` in
CSP): prevents clickjacking via iframe embedding. `SAMEORIGIN` unless
the site genuinely needs to be embeddable elsewhere.
- **Content-Security-Policy**: the highest-value header but also the
easiest to get wrong and break the site with. Build it FROM the site's
actual real asset sources (script/style/font/image origins actually in
use), don't write a generic policy and hope — a too-strict CSP silently
breaks functionality (a blocked script just doesn't run, often with no
obvious visual error). Start in `Content-Security-Policy-Report-Only`
mode if there's any uncertainty, confirm no violations in real browser
testing, then switch to enforcing.
- **CORS** (`Access-Control-Allow-Origin`): never `*` on any endpoint
that reads authenticated/session state — an open CORS policy on an
authenticated endpoint is a real, exploitable vulnerability, not a
theoretical one.
## TLS/SSL configuration strength
- **Protocol versions**: TLS 1.2 minimum, TLS 1.3 preferred/available.
SSLv3, TLS 1.0, and TLS 1.1 should be disabled — they're broken or
deprecated, not just "older."
- **Cipher suites**: prefer AEAD ciphers (AES-GCM, ChaCha20-Poly1305).
Disable known-weak ciphers (RC4, 3DES, export-grade ciphers, anything
without forward secrecy).
- **Certificate**: verify the chain is complete (intermediate certs
included, not just the leaf), verify auto-renewal is actually working
(a cert 5 days from expiry with no renewal cron running is a real
incident waiting to happen, not a hypothetical), verify no mixed-
content warnings (HTTP resources loaded on an HTTPS page).
- **Test with a real tool**, don't eyeball a config file and assume it's
correct: `openssl s_client -connect host:443 -tls1` (should fail if
TLS 1.0 is properly disabled), or an external scanner (SSL Labs'
`ssltest` API/site) for a full grade — config files that *look* right
can still have an inherited default or a reverse-proxy layer
re-enabling something the origin server itself disabled.
## Reverse-proxy-specific gotchas
If the site sits behind a reverse proxy (nginx, a proxy manager, a CDN):
security headers and TLS termination often happen at the PROXY layer,
not the origin — verify headers on the actual public-facing response,
not just what the origin server itself sends, since a proxy can add,
strip, or override headers in either direction. Confirmed real pattern
in this project: `X-Forwarded-Proto` set by the proxy and trusted by the
origin's own HTTPS-detection logic — get this wrong in either direction
(proxy not setting it, or origin not trusting it, or origin overwriting
an already-correct value with its own guess) and you get either broken
HTTPS detection or a spoofable header, depending on which mistake it is.
## Don't apply hardening blindly
Test every header/TLS change against the real site afterward — a CSP
that's too strict, an HSTS header sent too early, or a cipher suite
change that drops support for a client that still needs to work are all
real ways "hardening" can cause an outage. Security work still needs the
same verification discipline as any other change.
+76
View File
@@ -0,0 +1,76 @@
---
name: web-accessibility
description: Use when building or reviewing any user-facing web page/UI. Baseline WCAG-aligned accessibility practices to apply by default, not just when explicitly requested.
---
# Web Accessibility
Apply these by default on any real user-facing page — accessibility
issues are usually cheap to avoid at build time and expensive to retrofit.
## Semantic HTML first
Use the element that actually means what you're building, before
reaching for ARIA to patch a `<div>` into behaving like something else:
`<button>` for actions (not a `<div onclick>`), `<a href>` for
navigation, real heading levels (`<h1>`-`<h6>`) in a logical, non-skipped
order, `<nav>`/`<main>`/`<footer>`/`<article>` landmarks. A page built
from semantic elements gets most of its accessibility for free; a page
built entirely from `<div>`s needs everything added back manually via
ARIA, which is more work and easier to get subtly wrong.
## Images and media
- Every meaningful `<img>` needs real, specific `alt` text (not the
filename, not "image123") — describe what the image conveys, not that
it's an image.
- Purely decorative images: `alt=""` (empty, not omitted) so screen
readers skip them silently instead of reading a filename.
- Video/audio with meaningful content needs captions/transcripts.
## Keyboard navigation
- Everything clickable must be reachable and operable via keyboard alone
(Tab to focus, Enter/Space to activate) — test this literally, don't
assume a `<button>` or `<a>` is automatically fine (it usually is;
custom interactive widgets built from other elements usually aren't
without explicit `tabindex`/keydown handling).
- Visible focus indicators — never `outline: none` without providing a
real, visible replacement focus style. Removing focus indicators
entirely is a common and serious accessibility regression.
- Logical tab order matching visual/reading order.
## Color and contrast
- Text contrast ratio ≥ 4.5:1 for normal text, ≥ 3:1 for large text
(WCAG AA). Check this for real against the actual rendered colors, not
just "looks readable to me" — subtle brand colors on dark backgrounds
are a common failure point.
- Never convey information (an error state, a required field, a status)
through color alone — pair it with text, an icon, or a pattern.
## Forms
- Every input has a programmatically-associated `<label>` (via `for`/
`id`, or wrapping) — not just visual proximity.
- Error messages are associated with their field (`aria-describedby`) and
announced to assistive tech, not just visually styled red text.
- Required fields marked both visually and via the `required` attribute
or `aria-required`.
## ARIA: use sparingly, and correctly
ARIA attributes override the browser's default accessibility mapping —
using them incorrectly can make a page *less* accessible than using no
ARIA at all. The first rule of ARIA is "don't use ARIA if a native HTML
element already does what you need." When you do need it (custom
widgets: a modal, a tab panel, a combobox), match the established ARIA
Authoring Practices pattern for that widget type rather than improvising.
## Quick self-check before calling something accessible
- Can you operate the whole page with only a keyboard?
- Does every image have appropriate alt text?
- Do headings form a logical outline (one `<h1>`, nested levels not
skipped)?
- Does removing all color (grayscale) still leave the page understandable?
+66
View File
@@ -0,0 +1,66 @@
---
name: web-design-best-practices
description: Use when designing or implementing a UI from scratch, or reviewing one for quality. Modern web design/UX conventions to apply by default, not just when explicitly asked for "modern design."
---
# Web Design Best Practices
## Visual hierarchy and typography
- One clear primary action per screen/section — a page with 4 equally-
weighted CTAs has no CTA.
- Type scale: use a consistent, limited set of sizes (a modular scale,
not ad-hoc pixel values scattered through the CSS). Headline/body/
caption is usually enough tiers for a marketing page.
- Line length: body text at 60-75 characters per line max — full-width
text on a wide viewport is hard to read, constrain with `max-width`.
- Line height: 1.4-1.6 for body text, tighter (1.05-1.2) for large
display headlines.
## Spacing and layout
- Consistent spacing scale (e.g. 4px/8px increments), not arbitrary
margins per element — this is what makes a layout feel "designed"
rather than assembled ad-hoc.
- Generous whitespace around primary content — cramped layouts read as
low-quality regardless of the actual visual polish of individual
elements.
- Responsive by default: design/build mobile and desktop simultaneously,
not desktop-first-then-retrofit. A single-column collapse at a sensible
breakpoint (usually 600-900px) is the baseline, not an afterthought.
## Color and contrast
- A real design-token system (CSS custom properties for the brand
palette), not hardcoded hex values scattered through templates —
changing the brand color should be a one-line change, not a
find-and-replace across every file.
- Every accent/brand color needs a check against WCAG contrast ratios
where it's used for text or meaningful UI — see the
`web-accessibility` skill.
## Motion
- Subtle, purposeful, and **always gated behind
`prefers-reduced-motion`** — animation should communicate state change
(a hover, a loading state, an entrance), not be decorative for its own
sake. Anything that loops indefinitely (a marquee, a pulsing dot)
especially needs the reduced-motion escape hatch.
## Forms
- Label every input (visually, not just via `placeholder` — placeholder
text disappears the moment the user starts typing and isn't a
substitute for a real `<label>`).
- Inline validation feedback near the field it applies to, not just a
generic error banner at the top of the form.
- Clear loading/success/error states on submit — a button that just sits
there with no visual change while a request is in flight reads as
broken.
## Before calling a design "modern" or "done"
Actually look at it — screenshot it (real browser, real viewport sizes:
mobile ~390px, desktop ~1280px+) rather than trusting that correct CSS
implies correct visual result. A CSS bug that's invisible when reading
the source is often immediately obvious in a screenshot.