Skip to content

logging: support filtering Set-Cookie values in log fields #7887

Description

@steffenbusch

Issue Details

I have a feature request regarding Caddy's log field filters.

Caddy already has a built-in cookie log filter in modules/logging/filters.go with these actions:

  • hash
  • replace
  • delete

That works well for logged Cookie request header values.

A similar capability for logged Set-Cookie response header values in the access log field resp_headers>Set-Cookie would also be useful when log_credentials is enabled.

Use case

I would primarily like to hash cookie values in logged Set-Cookie headers, while keeping the rest of the logged value readable. (replace and delete would be useful as well.)

Example logged value:

  "resp_headers": {
    "Date": [
      "Mon, 13 Jul 2026 10:53:19 GMT"
    ],
    "Set-Cookie": [
      "session=secret-value; Path=/; Secure; HttpOnly; SameSite=Strict"
    ],
    ...
  },

Desired result for hash session:

  "resp_headers": {
    "Date": [
      "Mon, 13 Jul 2026 10:53:19 GMT"
    ],
    "Set-Cookie": [
      "session=31160254; Path=/; Secure; HttpOnly; SameSite=Strict"
    ],
    ...
  },

Why this needs separate handling

Cookie and Set-Cookie have different structures.

  • Cookie contains a list of cookie name/value pairs.
  • Set-Cookie contains one cookie plus its attributes per header value.

Because of that, the existing cookie filter logic does not map directly to Set-Cookie. It may still produce partially useful output, but attributes are not handled correctly. For example, Secure; can end up as Secure=;.

Possible shape

A dedicated set_cookie log filter, parallel to the existing cookie filter, could support the same actions:

  • hash
  • replace
  • delete

Possible Caddyfile usage:

log {
	format filter {
		fields {
			resp_headers>Set-Cookie set_cookie {
				hash session
				delete csrf
			}
		}
	}
}

Possible implementation direction

Roughly, this could work by parsing each logged Set-Cookie value individually, matching by cookie name, applying the configured action, and then rendering the transformed value back into a readable string.

For example:

func (f SetCookieFilter) Filter(in zapcore.Field) zapcore.Field {
	vals, ok := in.Interface.(internal.LoggableStringArray)
	if !ok {
		return in
	}

	transformed := make(internal.LoggableStringArray, 0, len(vals))

	for _, line := range vals {
		c, err := http.ParseSetCookie(line)
		if err != nil {
			transformed = append(transformed, line)
			continue
		}

		matched := false
		for _, a := range f.Actions {
			if c.Name != a.Name {
				continue
			}

			matched = true

			switch a.Type {
			case replaceAction:
				c.Value = a.Value
				transformed = append(transformed, c.String())

			case hashAction:
				c.Value = hash(c.Value)
				transformed = append(transformed, c.String())

			case deleteAction:
				// omit this Set-Cookie entry
			}

			break
		}

		if !matched {
			transformed = append(transformed, c.String())
		}
	}

	in.Interface = transformed

	return in
}

Question

Would this kind of set_cookie log filter be something you would accept in Caddy core?

If yes, I could work on a PR for it.
If not, I would probably implement it as an external plugin instead.

Assistance Disclosure

AI used

If AI was used, describe the extent to which it was used.

ChatGPT/Codex was used to help draft the issue text and improve phrasing. The feature request itself and the technical direction came from me.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions