--- name: coding-directives description: "Implement or review caddy-security Go code, Caddy modules, parsers, lifecycle, and HTTP delegation. Use for app/plugin boundaries, authcrunch integration, errors, logging, and code conventions." --- # Coding Directives ## Overview Apply these directives when editing or reviewing caddy-security code. Prefer small, idiomatic Go changes that preserve Caddy module boundaries, delegate auth logic to `go-authcrunch`, and keep parser behavior covered by focused tests and fixtures. The [testing contract](../testing-and-ci/SKILL.md) governs test selection and coverage. [Automation ownership](../scripts-and-automation/SKILL.md) covers Make targets, generated artifacts, and dependency/replacement workflows. After code changes, review and update the relevant repo-local skills and linked references in the same change so implementation and operational guidance match the final behavior. This is part of completing the code work; follow [keeping skills current](../skill-authoring/SKILL.md#keep-skills-current-after-code-changes) for scope, evidence, and cases where existing guidance remains accurate. Do not add Markdown documentation to `docs/` or `assets/docs/`; follow the [skill-authoring ownership guidance](../skill-authoring/references/caddy-security.md#ownership-and-routing) for documentation ownership and placement. ## Repository Scope Keep source changes inside `caddy-security`. Sibling repositories such as `../go-authcrunch` are read-only references and are updated separately. The sole sibling-write exception is `../xcaddy-caddy-security`, the integrated xcaddy build workspace. Creating, updating, building in, and cleaning that workspace is allowed when needed for the xcaddy workflow. The exception does not permit changes to sibling source modules referenced by the build, including go-authcrunch. No other sibling directory is a permitted write destination. This boundary covers creating, editing, deleting, restoring, staging, and committing files, including code, tests, fixtures, dependency files, skills, generated artifacts, and Git metadata. Outside the named xcaddy workspace, do not run sibling build, test, formatting, generation, license, dependency, or cleanup commands, or change those repositories' Git state through fetch, pull, checkout, reset, tag, or other Git mutations. Before a mutating command, confirm its repository root and working directory, inspect the invoked script's side effects, and resolve its output paths. A command launched from this repository can still write elsewhere. Do not bypass the boundary through symlinks, linked worktrees sharing a sibling's Git metadata, module replacement paths, or output-directory flags. Keep task files and chosen build/report destinations in this checkout's working areas, such as `tmp/`, `bin/`, and `.coverage/`, except for the named xcaddy build workspace. Resolve that workspace's physical path before cleanup so a symlink cannot redirect the exception into another sibling repository. Reading sibling source, skills, versions, and history is allowed. References to upstream APIs, tests, and integration wiring are context, not instructions to modify or run that project. A local Go replacement may select existing sibling source for tests of this module; it does not authorize sibling changes. If a fix requires an upstream change, document the affected contract and separate work needed, complete the work possible here, and state any validation blocker. Do not patch the sibling, duplicate its runtime here to avoid the boundary, or request to expand the current task into that repository. ## Architecture Treat `security` as the top-level Caddy app. Keep shared authcrunch configuration, provisioned portals, gatekeepers, identity stores, messaging, credentials, registration, and secrets managers owned by `App`. Treat `authenticate` and `authorize` as HTTP-facing integrations that attach to Caddy routes and resolve named runtime objects from the provisioned `security` app. Do not duplicate portal or gatekeeper behavior in the Caddy plugins when `go-authcrunch` already owns it. Keep the root package focused on Caddy app/plugin wiring and Caddyfile parsing. Use `pkg/util` only for small reusable helpers that are truly package-external or shared across multiple root-package files. ## Caddy Modules For runtime construction, ownership, request draining, reload, or cleanup work, read [Runtime lifecycle](references/runtime-lifecycle.md). It traces Caddy's host ordering, the app's disposal contract, identity-file ownership restrictions, and the unit/E2E tests that verify those behaviors. Register Caddy modules and Caddyfile directives in `init` functions near the module implementation. Provide a `CaddyModule` method with the correct public Caddy module ID and `New` constructor. Add interface guards for Caddy contracts such as `caddy.Module`, `caddy.Provisioner`, `caddy.Validator`, `caddy.App`, `caddyfile.Unmarshaler`, `caddyhttp.MiddlewareHandler`, and `caddyauth.Authenticator` when a type is expected to satisfy them. Keep exported configuration fields serializable with consistent struct tags. For HTTP middleware config fields, preserve matching `json`, `xml`, and `yaml` tags unless the surrounding type intentionally differs. Keep runtime-only fields unexported and untagged. In `Provision`, resolve the `security` app through Caddy context, validate app/config cases, apply Caddy replacer substitutions where needed, and validate named declarations. The default in-memory app can provide runtime objects during provisioning; persistent state defers root construction until `App.Start` owns storage. A declared portal or policy can therefore be valid before its runtime pointer exists. Preserve deferred lookup and request admission checks; do not reject persistent candidates merely because `Provision` has no runtime object. The [lifecycle contract](references/runtime-lifecycle.md) owns the ordering. ## Caddyfile Parsers Use [configuration-authentication-cross-device](../configuration-authentication-cross-device/SKILL.md) to implement or review cross-device directive aggregation, delegated parsing, approval boundaries and host/browser acceptance tests. Follow the existing parser shape: ```go func parseCaddyfileSurface(d *caddyfile.Dispenser, cfg *authcrunch.Config) error ``` Use `d.RemainingArgs()` to validate directive arguments, `d.Nesting()` with `d.NextBlock(nesting)` for blocks, and small helper functions for nested subdirectives. Use `mkcp` or the local directive-prefix pattern to build clear directive paths such as `security.authentication.portal.cookie`. Return `d.ArgErr()` for malformed top-level argument counts. Use `h.Errf` or `d.Errf` for Caddyfile parse errors that should include source locations. Use `go-authcrunch/pkg/errors` helpers where the surrounding parser already uses them for malformed directive values. Keep syntax comments above parser functions current when adding or changing directives **or updating a dependency that owns delegated grammar**. Document headers, body scope, argument counts, aliases, repetition, and the parser that owns deeper validation. Keep restricted forms visible with an explicit status; do not delete documented syntax merely because a shared validator rejects it. Update runnable examples and the owning configuration skill in the same change. Follow the [syntax maintenance workflow](../configuration/references/syntax-maintenance.md) for the source inventory and validation boundaries. Explain behavior alongside the syntax when a setting's name is insufficient. Include units/defaults, the meaning of omission/zero/disabled values, the scope of limits (per user, session, portal or process), inheritance and precedence, and consequential interactions or failure behavior. Explain the operational reason for a restriction or tradeoff when supported by the implementation: for example, whether a timeout slides, what consumes a session slot, or why native body transport requires explicit opt-in. Verify these details against the selected parser **and runtime**; field names alone do not establish them. Keep the grammar easy to scan, follow it with focused explanatory paragraphs, and link to the owning feature reference for longer protocol examples. Scale the detail to the feature instead of repeating a checklist for trivial options. When mapping Caddyfile input, prefer authcrunch config constructors and `Add*` methods over duplicating validation in this repository. Use `cfgutil.EncodeArgs` for raw instruction strings that authcrunch later decodes. Use `map[string]interface{}` only where authcrunch expects flexible parameter maps. Preserve exact error wording when tests assert it. Many parser tests compare `err.Error()` strings, including Caddy-added file and line suffixes. ## Runtime Config Keep Caddy replacer behavior centralized through `util.FindReplace`, `util.FindReplaceAll`, and `ResolveRuntimeAppConfig`. Add new replacement paths there when adapted JSON can contain `{env.*}` or secret placeholders that need runtime resolution. Treat `secrets::` values as sensitive. Resolve them through `SecretsManager` methods and avoid logging secret values, credentials, tokens, passwords, API keys, or private keys. Pass `context.Context` first when adding helpers that can touch secrets, external state, Caddy context, or request-scoped work. ## HTTP Handlers Before AuthCrunch consumes forwarded metadata, call `normalizeSecurityMetadata` from `request_metadata.go`. Use Caddy's `trusted_proxy` and `client_ip` context variables; do not reparse the proxy chain or synthesize Origin/TLS evidence. See [edge trust](../configuration-http-integrations/SKILL.md#edge-trust). `Gatekeeper.Authenticate` has two independent outputs: an error and response flags. `AuthorizationHandler` preserves a handled response and runs downstream only after explicit authorization or bypass; unhandled errors deny. The legacy authenticator remains available for manually written JSON, but Caddy's generic authentication chain cannot express a handled OAuth callback/logout. Current `authorize` directives emit `http.handlers.authorization`. Preserve handled status/body, mark denials and redirects no-store before headers commit, and never proceed upstream on nil error alone. `TestAuthzResponseContract` and the composed TLS suite check the actual protected handler boundary. Keep request handling thin. For `authenticate`, construct the authcrunch request object, attach `util.GetRequestID(r)`, and delegate to the portal. For `authorize`, delegate to the gatekeeper and only translate successful authcrunch authorization data into Caddy `caddyauth.User` metadata. For OP integration, retain the complete canonical request URL when calling `Portal.ServeHTTP`. It owns OIDC dispatch and completed-login evidence. Do not strip the issuer mount, preauthorize OP endpoints, call `CompleteLogin`, or reconstruct authentication evidence in Caddy middleware. Preserve the portal's status, headers and body; see the [OIDC HTTP contract](../configuration-oauth-applications/references/oidc-provider.md#http-mount-and-protocol-contract). The same dispatch owns refresh/session/logout credential authentication before access-token gates and serves the matching embedded browser client. Preserve headers (including the SID precondition), strict JSON failures, cookie deletion, no-store and continuation CSP. Never add automatic rotation retries or recover uncertainty with session lookup. See the [browser refresh contract](../authentication-portal-api/references/browser-refresh.md). When adding metadata, check presence before type assertions unless the upstream authcrunch contract guarantees the field. Keep metadata values string-based for Caddy compatibility. ## Errors And Logging Return errors instead of panicking. Include the directive path, operation, or named portal/gatekeeper/provider in errors so failures are actionable. Use `%w` when a caller may need to unwrap an error, but preserve existing `%v` or exact string formatting when tests or Caddyfile diagnostics depend on it. Use zap structured logging for app lifecycle and runtime diagnostics. Log identifiers, paths, directive names, and types; never log secrets or token payloads. Diagnostic suppression is governed by [configuration-logging](../configuration-logging/SKILL.md). Delegate rule parsing and immutable filters to AuthCrunch. Its root logger wrapping does not reach Caddy's private authentication middleware logger, and Caddy v2.11.7's custom cores only tee output. Keep that upstream limitation explicit; never change authentication results to silence a host log. ## Style Do not import or use Go's `reflect` package in repository Go code, including tests and helpers. Use explicit types, type switches, interfaces, or generics. Keep configuration validation typed instead of building runtime field walkers. Keep the Apache license header on Go files. Use package `security` for root application files and package `main` for executable entrypoints under `cmd/`, including `cmd/authcrunch` and `cmd/caddy-authenticator`. The standalone authenticator reuses the public authclient package; see its [maintenance reference](../scripts-and-automation/references/caddy-authenticator.md). Run `gofmt` on Go changes. Let Go tooling group imports into standard library, third-party packages, and local module packages. Use side-effect imports only for module registration or command bootstrapping, and keep the reason obvious from local context. Keep the existing license on edited Go files and include it in new Go files. `make license` rewrites all selected Go headers and README download links; run it only when that maintenance is part of the task. Skill-only edits require no license regeneration. Review any generated diff for unintended changes. Prefer small, unexported helpers for parser branches and runtime plumbing. Export only Caddy module types, public interfaces, and functions that are genuinely used outside the package. Use `const` groups for stable directive prefixes, plugin names, and repeated keywords. Avoid new global variables unless the value is intentionally mutable or computed. Write comments for exported identifiers and for non-obvious parser or runtime blocks. Avoid comments that merely restate the code. ## Tests And Fixtures Code changes require relevant unit tests and E2E tests that exercise the changed behavior under the [required coverage contract](../testing-and-ci/SKILL.md#required-coverage-for-code-changes). Add or amend missing coverage and run the applicable checks in this repository. Add focused parser coverage in the closest `caddyfile_*_test.go` when changing Caddyfile syntax or validation. Include malformed cases when the parser has a meaningful error path. For every Caddyfile directive change, add or amend adaptation cases in `testdata/caddyfile_adapt/` and register them in `caddyfile_adapt_test.go` as needed. Include `.Caddyfile`, expected `.json`, and optional `.env`; this requirement also applies when the JSON shape stays the same. Adaptation coverage supplements unit and E2E tests. Update `_resolved.json` and `TestResolveRuntimeAppConfig` coverage when runtime defaults, replacements, secrets, credentials, UI, OAuth, registration, cookie behavior, or any resolved authcrunch config output changes. Use `go-cmp` diffs or semantic JSON map comparison for test assertions. Avoid order-sensitive string comparisons for JSON unless the surrounding test already requires formatted output. After fixture or parser work, run the narrow relevant test first, then broaden according to the `testing-and-ci` skill. ## Acceptance criteria - Valid in-memory and persistent configurations follow their distinct construction timing. Requests cannot use a runtime before admission opens or after cleanup. - A rejected candidate preserves the serving app; cleanup drains admitted calls before releasing resources. Validate through the lifecycle unit/E2E surfaces. - Parser changes preserve encoded values, report malformed input, and have adaptation plus runtime/user-flow evidence where applicable. Skill-only work neither regenerates licenses nor mutates sibling source.