Add design/accessibility/security/sanitization skills
This commit is contained in:
@@ -43,6 +43,14 @@ than assuming the delegate can fetch it itself.
|
|||||||
as fact; how to actually re-verify.
|
as fact; how to actually re-verify.
|
||||||
- `karpathy-guidelines` -- general LLM-coding behavioral guidelines
|
- `karpathy-guidelines` -- general LLM-coding behavioral guidelines
|
||||||
(simplicity, surgical changes, surfacing assumptions).
|
(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
|
## Provenance
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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?
|
||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user