Class: Woods::Console::AuditLogger

Inherits:
Object
  • Object
show all
Defined in:
lib/woods/console/audit_logger.rb

Overview

Logs all Tier 4 tool invocations to a JSONL file.

Each line is a JSON object with: tool name, params, timestamp, confirmation status, and result summary.

Params and result summaries are passed through CredentialScanner so credentials an agent pastes inline into ‘console_eval` (or any other tool) do not land in audit logs unredacted.

Examples:

logger = AuditLogger.new(path: 'log/console_audit.jsonl')
logger.log(tool: 'console_eval', params: { code: '1+1' },
           confirmed: true, result_summary: '2')
logger.entries # => [{ "tool" => "console_eval", ... }]

Constant Summary collapse

MAX_FIELD_CHARS =

Soft cap on any single logged field. Stops an attacker with Tier-4 access from filling disk via arbitrarily long params.

16_384

Instance Method Summary collapse

Constructor Details

#initialize(path:, scanner: nil) ⇒ AuditLogger

Returns a new instance of AuditLogger.

Parameters:

  • path (String)

    Path to the JSONL audit log file

  • scanner (#scan, nil) (defaults to: nil)

    CredentialScanner override (mostly for tests).



32
33
34
35
# File 'lib/woods/console/audit_logger.rb', line 32

def initialize(path:, scanner: nil)
  @path = path
  @scanner = scanner || CredentialScanner.new
end

Instance Method Details

#entriesArray<Hash>

Read all audit entries.

Returns:

  • (Array<Hash>)

    Parsed JSONL entries



121
122
123
124
125
126
127
# File 'lib/woods/console/audit_logger.rb', line 121

def entries
  return [] unless File.exist?(@path)

  File.readlines(@path).filter_map do |line|
    JSON.parse(line.strip) unless line.strip.empty?
  end
end

#log(tool:, params:, confirmed:, result_summary:) ⇒ void

This method returns an undefined value.

Write an audit entry.

Parameters:

  • tool (String)

    Tool name

  • params (Hash)

    Tool parameters

  • confirmed (Boolean)

    Whether confirmation was granted

  • result_summary (String)

    Brief result description



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/woods/console/audit_logger.rb', line 44

def log(tool:, params:, confirmed:, result_summary:)
  ensure_directory!

  entry = {
    tool: tool,
    params: redact(truncate_deep(params)),
    confirmed: confirmed,
    result_summary: redact(truncate_value(result_summary)),
    timestamp: Time.now.utc.iso8601
  }

  # Exclusive flock around the append — concurrent Tier-4 invocations
  # across Puma threads would otherwise interleave bytes and produce
  # malformed JSONL lines (integrity hit on audit review).
  File.open(@path, File::WRONLY | File::APPEND | File::CREAT, 0o644) do |f|
    f.flock(File::LOCK_EX)
    f.puts(JSON.generate(sanitize_controls(entry)))
  end
end

#sizeInteger

Number of audit entries.

Returns:

  • (Integer)


132
133
134
# File 'lib/woods/console/audit_logger.rb', line 132

def size
  entries.size
end