The Craftsman standard for defensive security hardening — authorization policy (per-resource authZ, IDOR/tenant scoping), input validation & injection prevention, secrets, security headers, CORS, dependency vulnerabilities, XSS/CSRF, and data exposure. Use WHENEVER work touches security: harden an endpoint, review auth, handle secrets, lock down headers, audit dependencies, or production-harden a service. Trigger on "is this secure", "harden this", "review for vulnerabilities", or "handle secrets properly". Owns authZ, abuse-defense policy, and security review of auth flows — see "Scope boundaries" in the body for handoffs.
This skill encodes one engineer's standard for defensive security, applied the same way across every
repo. The method and opinions live here; the project specifics (which auth provider, which
secret store, which validation library) live in the target repo's code and config — always discover
them, never assume or hardcode.
Operating principle — discover before you build
Different repos already have different pieces in place. Before changing anything, spend a few minutes
mapping the current posture so you extend rather than conflict:
package.json / lockfile → which auth library, validation library, and HTTP framework are present?
grep for an existing env schema (env.ts, config.ts) — are secrets loaded through a validated
schema or read raw from process.env?
Check for an existing middleware file or proxy entry point — are security headers already set, and
where?
Scan package.json for known-vulnerable pinning patterns; note whether a dependency scanner
(npm audit, Snyk, Dependabot) is wired into CI.
Look at existing route handlers — is authorization checked once in middleware, per-route, or not
at all?
State what you found, then propose the smallest set of changes that closes the gaps.
The security layers (work in this order)
Authorization & auth-flow security — enforce least-privilege on every resource (a valid
session does not mean access to everything), and apply the security standard for the
authentication flow (JWT verification criteria: pin the algorithm, reject alg:none, check
exp/iss/aud). The authN implementation — verifying the session/token and resolving the
principal/tenant in the request lifecycle — is owned by craft-backend → auth.md; this layer
owns the criteria that verification must meet and the authZ policy on top. See
references/authz.md.
Input & output — validate all input at the boundary, encode all output for its target context,
parameterize all queries. Never trust data that crossed a trust boundary. See
references/input-output.md.
Secrets — credentials, tokens, and keys belong in a validated env schema or secret store, not
in source code, logs, or error messages. See references/secrets.md.
Transport & headers — TLS is table stakes; security headers (CSP, HSTS, X-Frame-Options,
etc.) and a strict CORS policy narrow the attack surface further. See references/headers-cors.md.
Supply chain — pinned, scanned dependencies with critical vulnerabilities blocking CI. Every
package you import is code you're responsible for. See references/supply-chain.md.
Data rights — a user-data deletion path exists and cascades, third-party processors get their
own deletion call, an export path exists, and PII is inventoried rather than leaking into logs or
error trackers. Engineering-observable only — not legal advice. See references/data-rights.md.
Standing opinions (the non-negotiables)
These are the judgments that make output consistent across repos — apply them unless the user
overrides:
Authorization is checked on every request at the resource boundary. Authentication (who you
are) is not the same as authorization (what you're allowed to do). Passing auth middleware doesn't
grant access to a resource; the resource handler confirms it.
All input is validated at the boundary, all output is context-encoded. SQL goes through
parameterized queries or an ORM, HTML output is escaped, JSON responses never leak internal fields
that weren't explicitly selected.
Secrets flow through a validated env schema and are never logged or exposed in errors. Raw
process.env reads are replaced with the schema-validated equivalent; error handlers scrub
credential-shaped strings before they hit logs or responses.
CORS is deny-by-default; CSP is explicit. Wildcard origins and missing Content-Security-Policy
headers are treated as gaps to close, not neutral defaults.
Dependencies are pinned and scanned in CI; criticals block merge. Unpinned ranges are a
supply-chain risk — lock them, run the scanner, and gate on the results.
This is the defensive-hardening standard. Pair it with a dedicated penetration-testing or
threat-modelling exercise when doing a full security review; that's a different discipline.
Workflow
Discover — map the current posture (auth provider, secret loading, headers, validation,
dependency scanner) and report the gaps.
Propose — ordered by the layers above, highest-risk gap first, smallest viable changes.
Implement — against the repo's existing patterns (its env schema, its middleware chain, its
validation library, its CI config).
Verify — test that authorization denials fire correctly, confirm headers are present in
responses, run a dependency scan and confirm it passes. Security you haven't seen enforce isn't
done.
Scope boundaries
This skill owns authorization policy and the security review of auth flows. Hand off at these
lines:
The authentication boundary itself (how a request is authenticated, where the principal is
resolved) → craft-backend. This skill owns authZ and reviews the authN flow for weaknesses.
Rate-limit ownership, so the same gap isn't emitted four times: SEC owns abuse-defense
policy — login throttling, brute-force, credential stuffing, lockout; the route middleware
mechanism → craft-backend; platform/edge capacity → craft-infra; LLM spend and token limits →
craft-ai.
When craft-audit plans a security pass for a scope, it turns this checklist into the plan.md
todo list — the checklist is owned by this skill, not improvised by the orchestrator. Tailor to what
discovery found: skip a step that genuinely doesn't apply with a one-line reason; never silently drop
one. Emit findings using craft-audit workspace.md → "Canonical findings.md emission format"
(authority). Heading grammar (variables required — do not hardcode NNN/severity/status):
## <scopeLabel>-SEC-<NNN> · severity <🔴|🟡|🟢> · status <open|fixed|wontfix (reason)|regressed|fixed (merged into <ID>)>
Example only: ## <scopeLabel>-SEC-001 · severity 🔴 · status open
Required fields under each heading, in order, with these exact labels:
**What breaks (plain language):** · **Technical:** · **Fix:** · **Fingerprint:** ·
**Last-checked:** (optional **Confidence:** — verified | inferred | unverified-from-repo, absent
means verified — then optional **Fix-attempt:** only from craft-fix).
Assign sequential NNN per (scope, domain); judge severity with craft-audit prioritization.md.
Forbidden: ### headings; ## ID · 🔴 · open shorthand; severity/status as body bullets.
Map the current posture — auth library, env loading, headers, validation lib, dependency
scanner — flagging raw process.env reads and authZ that's checked nowhere → SKILL.md
"Operating principle — discover before you build"
Verify authorization is enforced at the resource boundary on every request, not just authN;
hunt IDOR / broken object-level access where any session can reach another tenant's resource →
references/authz.md
Check JWT verification criteria — algorithm pinned, alg:none rejected, exp/iss/aud
validated — and that authZ source of truth (RBAC/ABAC) is server-side → references/authz.md
Confirm auth endpoints (login, password-reset, OTP, token) have abuse-defense rate-limit
policy (per-IP + per-account throttling / lockout). Ownership (emit once): SEC owns the
policy finding; BE owns missing in-app middleware mechanism; INFRA owns platform/edge capacity;
AI owns LLM spend limits — do not re-emit the same gap under all four → references/authz.md
Confirm all input is validated at the boundary, output is context-encoded, and queries are
parameterized; flag unescaped HTML, DOM XSS sinks, and SSRF on outbound requests →
references/input-output.md
Trace every secret through a validated env schema; flag credentials in source, logs, or error
responses, client-exposed env treated as private, and missing scrubbing → references/secrets.md
Verify security headers (CSP, HSTS, X-Frame-Options) are present in responses and CORS is
deny-by-default; flag wildcard origins and missing Content-Security-Policy →
references/headers-cors.md
CSRF: form submissions and state-mutation endpoints are protected (SameSite cookie or CSRF
token); SameSite=None session cookies with no additional CSRF defense are flagged →
references/input-output.md
Confirm dependencies are pinned and a scanner gates CI with criticals blocking merge; flag
unpinned ranges and unfixed/untriaged vulnerabilities → references/supply-chain.md
Run a one-pass license check (npx license-checker / pnpm licenses list); flag any GPL/AGPL
dependency with no replace/isolate/advice plan → references/supply-chain.md
Verify the user-data deletion path cascades to every related table (not just the primary row)
and includes deletion calls to third-party processors (Stripe, analytics, email) →
references/data-rights.md
Confirm a PII surface inventory exists (which tables/columns hold PII) and that PII isn't
leaking into application logs, analytics events, or error trackers (Sentry) →
references/data-rights.md
Compare the privacy policy's factual claims against the actual SDK init literals (DNT/consent
options, session-recording flags, cookie vs localStorage persistence) and its subprocessor list
against the third parties actually initialized. Verify library defaults in the pinned version
before calling an absent option a defect → craft-audit references/claim-verification.md
Check that the success message returned by destructive endpoints (account/data deletion) is
true of what the code actually did — flag "permanently deleted" on a handler that soft-deletes,
defers to an async webhook, or leaves org-owned rows intact →
craft-audit references/claim-verification.md
On localized apps, verify protected-route matchers actually match under every supported
locale, using the installed matcher's documented pattern syntax — never assume regex alternation
like (en|de|ar) is valid in a string pattern, since several matchers treat non-parameter path
text literally. Test one protected path per locale, positive and negative; flag only a
demonstrated mismatch that leaves a route unprotected → references/authz.md
1---2name: craft-security3description: The Craftsman standard for defensive security hardening — authorization policy (per-resource authZ, IDOR/tenant scoping), input validation & injection prevention, secrets, security headers, CORS, dependency vulnerabilities, XSS/CSRF, and data exposure. Use WHENEVER work touches security: harden an endpoint, review auth, handle secrets, lock down headers, audit dependencies, or production-harden a service. Trigger on "is this secure", "harden this", "review for vulnerabilities", or "handle secrets properly". Owns authZ, abuse-defense policy, and security review of auth flows — see "Scope boundaries" in the body for handoffs.4---56# Security Craft78This skill encodes one engineer's standard for defensive security, applied the same way across every9repo. The **method and opinions** live here; the **project specifics** (which auth provider, which10secret store, which validation library) live in the target repo's code and config — always discover11them, never assume or hardcode.1213## Operating principle — discover before you build1415Different repos already have different pieces in place. Before changing anything, spend a few minutes16mapping the current posture so you extend rather than conflict:1718- `package.json` / lockfile → which auth library, validation library, and HTTP framework are present?19- `grep` for an existing env schema (`env.ts`, `config.ts`) — are secrets loaded through a validated20 schema or read raw from `process.env`?21- Check for an existing middleware file or proxy entry point — are security headers already set, and22 where?23- Scan `package.json` for known-vulnerable pinning patterns; note whether a dependency scanner24 (`npm audit`, Snyk, Dependabot) is wired into CI.25- Look at existing route handlers — is authorization checked once in middleware, per-route, or not26 at all?2728State what you found, then propose the smallest set of changes that closes the gaps.2930## The security layers (work in this order)31321. **Authorization & auth-flow security** — enforce least-privilege on every resource (a valid33 session does not mean access to everything), and apply the security *standard* for the34 authentication flow (JWT verification criteria: pin the algorithm, reject `alg:none`, check35 `exp`/`iss`/`aud`). The authN *implementation* — verifying the session/token and resolving the36 principal/tenant in the request lifecycle — is owned by **craft-backend** → `auth.md`; this layer37 owns the *criteria that verification must meet* and the authZ policy on top. See38 `references/authz.md`.392. **Input & output** — validate all input at the boundary, encode all output for its target context,40 parameterize all queries. Never trust data that crossed a trust boundary. See41 `references/input-output.md`.423. **Secrets** — credentials, tokens, and keys belong in a validated env schema or secret store, not43 in source code, logs, or error messages. See `references/secrets.md`.444. **Transport & headers** — TLS is table stakes; security headers (CSP, HSTS, X-Frame-Options,45 etc.) and a strict CORS policy narrow the attack surface further. See `references/headers-cors.md`.465. **Supply chain** — pinned, scanned dependencies with critical vulnerabilities blocking CI. Every47 package you import is code you're responsible for. See `references/supply-chain.md`.486. **Data rights** — a user-data deletion path exists and cascades, third-party processors get their49 own deletion call, an export path exists, and PII is inventoried rather than leaking into logs or50 error trackers. Engineering-observable only — not legal advice. See `references/data-rights.md`.5152## Standing opinions (the non-negotiables)5354These are the judgments that make output consistent across repos — apply them unless the user55overrides:5657- **Authorization is checked on every request at the resource boundary.** Authentication (who you58 are) is not the same as authorization (what you're allowed to do). Passing auth middleware doesn't59 grant access to a resource; the resource handler confirms it.60- **All input is validated at the boundary, all output is context-encoded.** SQL goes through61 parameterized queries or an ORM, HTML output is escaped, JSON responses never leak internal fields62 that weren't explicitly selected.63- **Secrets flow through a validated env schema and are never logged or exposed in errors.** Raw64 `process.env` reads are replaced with the schema-validated equivalent; error handlers scrub65 credential-shaped strings before they hit logs or responses.66- **CORS is deny-by-default; CSP is explicit.** Wildcard origins and missing Content-Security-Policy67 headers are treated as gaps to close, not neutral defaults.68- **Dependencies are pinned and scanned in CI; criticals block merge.** Unpinned ranges are a69 supply-chain risk — lock them, run the scanner, and gate on the results.7071This is the defensive-hardening standard. Pair it with a dedicated penetration-testing or72threat-modelling exercise when doing a full security review; that's a different discipline.7374## Workflow75761. **Discover** — map the current posture (auth provider, secret loading, headers, validation,77 dependency scanner) and report the gaps.782. **Propose** — ordered by the layers above, highest-risk gap first, smallest viable changes.793. **Implement** — against the repo's existing patterns (its env schema, its middleware chain, its80 validation library, its CI config).814. **Verify** — test that authorization denials fire correctly, confirm headers are present in82 responses, run a dependency scan and confirm it passes. Security you haven't seen enforce isn't83 done.8485## Scope boundaries8687This skill owns authorization *policy* and the security review of auth flows. Hand off at these88lines:8990- **The authentication boundary itself** (how a request is authenticated, where the principal is91 resolved) → `craft-backend`. This skill owns authZ and reviews the authN flow for weaknesses.92- **Rate-limit ownership, so the same gap isn't emitted four times:** SEC owns abuse-defense93 *policy* — login throttling, brute-force, credential stuffing, lockout; the route *middleware*94 mechanism → `craft-backend`; platform/edge capacity → `craft-infra`; LLM spend and token limits →95 `craft-ai`.96- **Whole-project readiness** → `craft-audit`.97- **Existing tracked findings** ("fix SEC-003") → `craft-fix`.9899## Reference index100101Read the one matching the current task — they hold the concrete setup, not this overview:102103- `references/authz.md` — authentication vs authorization, per-resource enforcement, JWT claims,104 IDOR / broken object-level authorization (OWASP BOLA)105- `references/input-output.md` — validation at the boundary, output encoding, parameterized queries,106 injection prevention107- `references/secrets.md` — env schema patterns, secret store integration, scrubbing secrets from108 logs and error responses109- `references/headers-cors.md` — CSP, HSTS, CORS deny-by-default, middleware placement110- `references/supply-chain.md` — dependency pinning, CI scanning, vulnerability triage thresholds111- `references/data-rights.md` — deletion path cascade, third-party processor deletion, export path,112 PII surface inventory (engineering-observable slice only, not legal advice)113114## Audit checklist (for craft-audit)115116When `craft-audit` plans a security pass for a scope, it turns this checklist into the `plan.md`117todo list — the checklist is owned by this skill, not improvised by the orchestrator. Tailor to what118discovery found: skip a step that genuinely doesn't apply with a one-line reason; never silently drop119one. Emit findings using craft-audit `workspace.md` → "Canonical findings.md emission format"120(authority). Heading grammar (variables required — do not hardcode NNN/severity/status):121122`## <scopeLabel>-SEC-<NNN> · severity <🔴|🟡|🟢> · status <open|fixed|wontfix (reason)|regressed|fixed (merged into <ID>)>`123124Example only: `## <scopeLabel>-SEC-001 · severity 🔴 · status open`125126Required fields under each heading, in order, with these exact labels:127`**What breaks (plain language):**` · `**Technical:**` · `**Fix:**` · `**Fingerprint:**` ·128`**Last-checked:**` (optional `**Confidence:**` — `verified | inferred | unverified-from-repo`, absent129means `verified` — then optional `**Fix-attempt:**` only from craft-fix).130Assign sequential NNN per (scope, domain); judge severity with craft-audit `prioritization.md`.131Forbidden: `###` headings; `## ID · 🔴 · open` shorthand; severity/status as body bullets.132133- [ ] Map the current posture — auth library, env loading, headers, validation lib, dependency134 scanner — flagging raw `process.env` reads and authZ that's checked nowhere → SKILL.md135 "Operating principle — discover before you build"136- [ ] Verify authorization is enforced at the resource boundary on every request, not just authN;137 hunt IDOR / broken object-level access where any session can reach another tenant's resource →138 `references/authz.md`139- [ ] Check JWT verification criteria — algorithm pinned, `alg:none` rejected, `exp`/`iss`/`aud`140 validated — and that authZ source of truth (RBAC/ABAC) is server-side → `references/authz.md`141- [ ] Confirm auth endpoints (login, password-reset, OTP, token) have abuse-defense rate-limit142 *policy* (per-IP + per-account throttling / lockout). **Ownership (emit once):** SEC owns the143 policy finding; BE owns missing in-app middleware mechanism; INFRA owns platform/edge capacity;144 AI owns LLM spend limits — do not re-emit the same gap under all four → `references/authz.md`145- [ ] Confirm all input is validated at the boundary, output is context-encoded, and queries are146 parameterized; flag unescaped HTML, DOM XSS sinks, and SSRF on outbound requests →147 `references/input-output.md`148- [ ] Trace every secret through a validated env schema; flag credentials in source, logs, or error149 responses, client-exposed env treated as private, and missing scrubbing → `references/secrets.md`150- [ ] Verify security headers (CSP, HSTS, X-Frame-Options) are present in responses and CORS is151 deny-by-default; flag wildcard origins and missing Content-Security-Policy →152 `references/headers-cors.md`153- [ ] CSRF: form submissions and state-mutation endpoints are protected (SameSite cookie or CSRF154 token); SameSite=None session cookies with no additional CSRF defense are flagged →155 `references/input-output.md`156- [ ] Confirm dependencies are pinned and a scanner gates CI with criticals blocking merge; flag157 unpinned ranges and unfixed/untriaged vulnerabilities → `references/supply-chain.md`158- [ ] Run a one-pass license check (`npx license-checker` / `pnpm licenses list`); flag any GPL/AGPL159 dependency with no replace/isolate/advice plan → `references/supply-chain.md`160- [ ] Verify the user-data deletion path cascades to every related table (not just the primary row)161 and includes deletion calls to third-party processors (Stripe, analytics, email) →162 `references/data-rights.md`163- [ ] Confirm a PII surface inventory exists (which tables/columns hold PII) and that PII isn't164 leaking into application logs, analytics events, or error trackers (Sentry) →165 `references/data-rights.md`166- [ ] Compare the privacy policy's factual claims against the actual SDK init literals (DNT/consent167 options, session-recording flags, cookie vs localStorage persistence) and its subprocessor list168 against the third parties actually initialized. Verify library defaults in the *pinned* version169 before calling an absent option a defect → craft-audit `references/claim-verification.md`170- [ ] Check that the success message returned by destructive endpoints (account/data deletion) is171 true of what the code actually did — flag "permanently deleted" on a handler that soft-deletes,172 defers to an async webhook, or leaves org-owned rows intact →173 craft-audit `references/claim-verification.md`174- [ ] On localized apps, verify protected-route matchers actually match under **every** supported175 locale, using the installed matcher's documented pattern syntax — never assume regex alternation176 like `(en|de|ar)` is valid in a string pattern, since several matchers treat non-parameter path177 text literally. Test one protected path per locale, positive and negative; flag only a178 demonstrated mismatch that leaves a route unprotected → `references/authz.md`179
Run npx skillmds@latest add gul-labs/craft-security in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
The Craftsman standard for defensive security hardening — authorization policy (per-resource authZ, IDOR/tenant scoping), input validation & injection prevention, secrets, security headers, CORS, dependency vulnerabilities, XSS/CSRF, and data exposure. Use WHENEVER work touches security: harden an endpoint, review auth, handle secrets, lock down headers, audit dependencies, or production-harden a service. Trigger on "is this secure", "harden this", "review for vulnerabilities", or "handle secrets properly". Owns authZ, abuse-defense policy, and security review of auth flows — see "Scope boundaries" in the body for handoffs. It is listed under Security on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
gul-labs (@gul-labs) published this skill. Their other Agent Skills are listed on their SkillMD profile.