Skip to content

WorkflowAuditor leaks the audit log file descriptor #6094

Description

@jhrozek

Found while reviewing #6046.

Problem

NewWorkflowAuditor opens the audit log through config.GetLogWriter():

// pkg/audit/workflow_auditor.go:33
logWriter, err := config.GetLogWriter()

When Config.LogFile is set, GetLogWriter returns an os.OpenFile handle (pkg/audit/config.go:69-81). But WorkflowAuditor has no Close method — the HTTP Auditor does:

// pkg/audit/auditor.go:109-114
func (a *Auditor) Close() error {
    if closer, ok := a.logWriter.(io.Closer); ok {
        return closer.Close()
    }
    return nil
}

So a file-backed WorkflowAuditor holds the descriptor for the process lifetime with no way for the owner to release it. WorkflowAuditor does not even retain the writer, so the fd is unreachable once the constructor returns.

Impact

One leaked fd per WorkflowAuditor instance. Bounded and low severity if the auditor is a long-lived singleton; it matters if one is ever constructed per workflow, per reconcile, or in tests that build many.

Suggested fix

Add Close() error mirroring Auditor.Close() (which means retaining the writer on the struct), and call it wherever the workflow auditor's lifetime ends.

While there, note a pre-existing hazard worth guarding in both types: with LogFile empty, GetLogWriter returns os.Stdout, which satisfies io.Closer — so Close() on a stdout-backed auditor closes fd 1. Auditor.Close() has this today; a new WorkflowAuditor.Close() should not inherit it. Skipping the close when the writer is os.Stdout fixes both.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions