dotkernel-security
Use when touching anything security-relevant in a Dotkernel project — authentication, OAuth2 or session login, authorization config and role permissions or guards, CORS, content negotiation, session a
SKILL.md.claude/skills/dotkernel-security/SKILL.md
GitHubSecurity
Applies to
Projects: - all
Packages: - none
Assume every request is hostile. Ignoring security is cheap today and expensive later — lost data, a GDPR fine, lost client trust. Every item below has a real place in the codebase; check it rather than reinventing it.
Input validation has its own skill (dotkernel-input-validation) — that is the first line and
the most commonly skipped.
Authentication
| Variant | Mechanism |
|---|---|
| API | OAuth 2.0 (mezzio/mezzio-authentication-oauth2) — access + refresh tokens |
| Admin / Frontend | session-based login |
| Light / Queue | none in the stock repositories |
For OAuth2 projects:
- Replace or update the default clients before production.
- Review token lifetimes against requirements (commonly one day access, one month refresh); do not lengthen without discussion.
- Keys are generated by the repo's key-generation script into a data directory. Never commit them, never edit them, never read them into context.
For session-based projects: cookie_httponly, cookie_samesite and cookie_secure must stay hardened in
the session config, and every form needs CSRF.
Authorization
| Variant | Where |
|---|---|
| API | RBAC roles and route-name permissions in config/autoload/authorization.global.php |
| Admin | roles → permissions in authorization.global.php; route name → permission in authorization-guards.global.php |
| Frontend | the same two files; guards keyed by route and controller action |
| Light / Queue | none |
- The default differs, and it is the dangerous part. The API denies a route that has no
permission entry. The Admin and Frontend guards use
POLICY_ALLOW: a route or action with no guard rule is open to everyone, guests included. Forgetting the entry there never surfaces as a 403 — it surfaces as an unprotected page. - Every new route needs an explicit entry — per role in the API, a guard rule in Admin and Frontend.
- Do not grant anything to a guest role unless the route is genuinely public (registration, activation, password recovery).
- Check the role hierarchy — inheritance direction in these config files is not always what people assume. Read the comment at the top of the file.
- Route-level access is not object-level access. A user permitted to view "an account" must only see their own. Use the resource guard mechanism where the project provides one, or an explicit ownership check in the service. This is the most common real vulnerability in CRUD code.
Configuration and secrets
| File | Committed? | May contain secrets? |
|---|---|---|
*.global.php |
yes | no |
*.php.dist |
yes | no — placeholders only |
*.local.php |
no (git-ignored) | yes |
Never write a credential, key, token, password-bearing DSN or SMTP password into a committed
file. Need a new setting? Add the placeholder to the .dist and read it through config.
Development mode enables debug output, error details and disables config caching. It must be disabled in production.
HTTP-layer protections
Configured in config/pipeline.php and config/autoload/ — keep the order, and justify any
change to it:
- CORS (where present): must be piped before the routing middleware. Never widen origins
to
*in a committed file. - Content negotiation: honours
Content-TypeandAccept; do not bypass it. - Malformed body handling: rejects unparseable payloads early.
- Error handling: outermost layer, renders a safe error body. Throw typed exceptions; never leak stack traces, SQL, paths or internal class names.
- Response headers: security headers are set centrally, not per handler.
Persistence
Use the query builder with bound parameters. No string concatenation or interpolation into DQL or SQL, ever. Whitelist user-supplied sort and filter values against an explicit column list. If you believe you need raw SQL, explain why and ask.
Error-reporting endpoints
Where the project exposes one, it is gated by a token and a permission middleware. Do not disable the token check or widen allowed IPs in a committed file.
Documentation exposure
Never document a real credential, internal hostname, or an example payload containing production data. Interactive documentation should not be enabled in production.
Dependencies and supply chain
- Adding, removing or upgrading a Composer or npm package requires explicit user approval. Propose it, name the maintainer, and say why the existing stack cannot cover the need.
- The security-advisories dev dependency refuses known-vulnerable versions. If it blocks an install, that is a signal, not an obstacle to route around.
- Let the automated dependency bot drive routine bumps.
- JavaScript is a live supply-chain target — widely used npm packages have been compromised. Use
npm auditand prefer packages from reliable sources. - Transitive dependencies count too.
Review checklist for a diff
- Every write path validates before business logic.
- Every new route has an explicit authorization entry; no accidental guest access.
- Object-level ownership checked where a resource belongs to a user.
- CSRF present and validated on every form (templated projects).
- No secrets outside
*.local.php; no keys committed. - No raw SQL/DQL interpolation; all parameters bound.
- Sort/filter values whitelisted;
limitclamped. - Uploads validated and stored outside executable paths.
- Errors thrown as typed exceptions with no internal detail.
- No new dependency without approval.
- Pipeline order unchanged, or the change justified.