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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssue needs initial triage by a maintainer

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions