Skip to content

feat(audit): integrate audit log recording into adapter layer#194

Merged
fohte merged 3 commits intofohte/audit-logfrom
fohte/impl-runok-audit-log-adapter
Mar 12, 2026
Merged

feat(audit): integrate audit log recording into adapter layer#194
fohte merged 3 commits intofohte/audit-logfrom
fohte/impl-runok-audit-log-adapter

Conversation

@fohte
Copy link
Copy Markdown
Owner

@fohte fohte commented Mar 12, 2026

Why

What

  • Add audit_metadata() and is_auditable() methods to the Endpoint trait, allowing each adapter to provide audit log metadata
  • Record audit log entries in run_with_options() after command evaluation (exec/hook only; dry-run and check are excluded)
  • Report write failures to stderr and continue processing (fail-open)

Open with Devin

Add `audit_metadata()` and `is_auditable()` methods to the `Endpoint`
trait and implement them for `ExecAdapter` (exec) and
`ClaudeCodeHookAdapter` (hook). `CheckAdapter` remains non-auditable.

Record audit log entries in `run_with_options()` after command evaluation
but before action dispatch. Compound commands include per-sub-command
evaluation results as `sub_evaluations`. Dry-run mode and non-auditable
endpoints skip recording. Write failures are reported to stderr without
affecting the exit code (fail-open).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request integrates a comprehensive audit logging mechanism into the core command evaluation flow. It enables detailed recording of command executions and their outcomes, providing crucial visibility into system activity. The changes ensure that auditable actions are logged with relevant metadata, while non-auditable or dry-run operations are appropriately excluded, enhancing security and operational transparency without impacting core functionality on failure.

Highlights

  • Audit Log Integration: Introduced audit log recording capabilities into the adapter layer, ensuring command evaluations are automatically logged.
  • Endpoint Trait Extension: Extended the Endpoint trait with audit_metadata() and is_auditable() methods, allowing adapters to provide specific audit information and control logging behavior.
  • Conditional Logging: Implemented automatic audit log entry recording within the run_with_options() function specifically for exec and hook command evaluations, explicitly excluding dry-run and check operations.
  • Fail-Open Mechanism: Ensured that audit log write failures are reported to stderr but do not halt program execution, adhering to a fail-open principle for system resilience.
  • Sub-Evaluation Logging: Added support for collecting and logging detailed sub-evaluations for compound commands within audit entries, providing deeper insight into complex operations.
Changelog
  • src/adapter/check_adapter.rs
    • Updated ActionResult instantiation in tests to include new fields for audit logging.
    • Added a test case to verify that is_auditable correctly returns false for the CheckAdapter.
  • src/adapter/exec_adapter.rs
    • Imported AuditMetadata for use in audit log entries.
    • Implemented the audit_metadata method for ExecAdapter to provide exec-specific details like endpoint type and current working directory.
    • Implemented the is_auditable method for ExecAdapter to return true, indicating it should produce audit logs.
    • Updated ActionResult instantiation in various tests to include new fields (matched_rules, sub_evaluations).
    • Added new tests specifically for audit_metadata and is_auditable functionality.
  • src/adapter/hook_adapter.rs
    • Imported AuditMetadata for use in audit log entries.
    • Implemented the audit_metadata method for ClaudeCodeHookAdapter to provide hook-specific details such as session ID, CWD, tool name, and hook event name.
    • Implemented the is_auditable method for ClaudeCodeHookAdapter to return true, indicating it should produce audit logs.
    • Updated ActionResult instantiation in tests to include new fields (matched_rules, sub_evaluations).
    • Added new tests specifically for audit_metadata and is_auditable functionality.
  • src/adapter/mod.rs
    • Imported several audit-related types (AuditEntry, AuditMetadata, AuditWriter, SerializableAction, SerializableRuleMatch, SubEvaluation).
    • Extended the ActionResult struct with matched_rules and sub_evaluations fields to store data relevant for audit logging.
    • Defined a new SubEvalResult struct for intermediate storage of sub-evaluation outcomes.
    • Added audit_metadata and is_auditable methods to the Endpoint trait, providing default implementations.
    • Introduced the collect_sub_evaluations function to gather audit data for individual commands within a compound command.
    • Implemented the write_audit_log function to serialize and write audit entries to the configured log path, including error handling for write failures.
    • Modified run_with_options to collect sub-evaluations for compound commands and populate the ActionResult.
    • Updated run_with_options to correctly populate matched_rules and sub_evaluations in the ActionResult for all evaluation paths.
    • Integrated the write_audit_log call into run_with_options, ensuring logs are written only for auditable endpoints and not in dry-run mode.
    • Added a new AuditableMockEndpoint struct to facilitate comprehensive testing of audit logging behavior.
    • Included extensive integration tests for various audit logging scenarios, covering enabled/disabled states, dry-run mode, auditable/non-auditable endpoints, different action types (allow, deny, default), audit log write failures, and compound command sub-evaluations.
  • src/rules/rule_engine.rs
    • Derived the Clone trait for the Action and DenyResponse enums to allow for their duplication when constructing audit log entries.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

gemini-code-assist[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment thread src/adapter/mod.rs Outdated

#[rstest]
fn audit_log_written_for_auditable_endpoint_on_match() {
let audit_dir = tempfile::TempDir::new().unwrap();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 CLAUDE.md violation: repeated tempfile::TempDir::new().unwrap() setup across 7 tests not extracted into #[fixture]

The CLAUDE.md rule "Use #[fixture] for shared test setup" states: "Do not repeat the same setup code across tests. Extract into #[fixture]." The let audit_dir = tempfile::TempDir::new().unwrap(); pattern is repeated identically in 7 test functions (lines 1155, 1181, 1200, 1222, 1244, 1263, 1305). The same codebase already follows the fixture pattern correctly in src/audit/writer.rs:76-79 with #[fixture] fn audit_dir() -> TempDir { TempDir::new().unwrap() }. The adapter mod tests should follow the same convention.

Prompt for agents
In src/adapter/mod.rs, in the #[cfg(test)] mod tests block, add a #[fixture] for audit_dir (similar to the existing one in src/audit/writer.rs:76-79):

#[fixture]
fn audit_dir() -> tempfile::TempDir {
    tempfile::TempDir::new().unwrap()
}

Then update each of the 7 test functions that currently have `let audit_dir = tempfile::TempDir::new().unwrap();` to instead accept audit_dir as a parameter. The affected test functions are:
- audit_log_written_for_auditable_endpoint_on_match (line 1154)
- audit_log_not_written_for_non_auditable_endpoint (line 1180)
- audit_log_not_written_in_dry_run_mode (line 1199)
- audit_log_not_written_when_disabled (line 1221)
- audit_log_records_deny_action (line 1243)
- audit_log_records_default_action_for_no_match (line 1262)
- audit_log_records_compound_sub_evaluations (line 1304)

Also add `use rstest::fixture;` to the existing imports if not already present.

For example, change:
#[rstest]
fn audit_log_written_for_auditable_endpoint_on_match() {
    let audit_dir = tempfile::TempDir::new().unwrap();

To:
#[rstest]
fn audit_log_written_for_auditable_endpoint_on_match(audit_dir: tempfile::TempDir) {
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 12, 2026

Codecov Report

❌ Patch coverage is 95.54140% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.09%. Comparing base (f54668f) to head (a8236e4).
⚠️ Report is 1 commits behind head on fohte/audit-log.

Files with missing lines Patch % Lines
src/adapter/mod.rs 94.57% 7 Missing ⚠️
Additional details and impacted files
@@                 Coverage Diff                 @@
##           fohte/audit-log     #194      +/-   ##
===================================================
+ Coverage            89.83%   90.09%   +0.26%     
===================================================
  Files                   34       34              
  Lines                 6885     7039     +154     
===================================================
+ Hits                  6185     6342     +157     
+ Misses                 700      697       -3     
Flag Coverage Δ
macOS 91.83% <95.54%> (+0.23%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Return per-sub-command evaluation results from `evaluate_compound`
via `CompoundEvalResult::sub_results` to avoid re-evaluating
sub-commands for audit logging.

Extract `read_single_audit_entry` helper and `#[fixture] audit_dir`
to reduce test duplication.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment thread src/adapter/mod.rs Outdated
Comment on lines +1188 to +1192
let entries: Vec<_> = std::fs::read_dir(audit_dir.path()).unwrap().collect();
assert!(
entries.is_empty(),
"no audit log should be written for non-auditable endpoint"
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 CLAUDE.md violation: repeated assertion chain in 3+ tests not extracted into helper

The CLAUDE.md rule "Extract repeated assertions into helper functions" states: "If the same assertion chain appears in 3+ tests, extract it into a helper." The assertion chain let entries: Vec<_> = std::fs::read_dir(audit_dir.path()).unwrap().collect(); assert!(entries.is_empty(), ...); appears identically in exactly 3 tests: audit_log_not_written_for_non_auditable_endpoint (line 1188), audit_log_not_written_in_dry_run_mode (line 1209), and audit_log_not_written_when_disabled (line 1230). This should be extracted into a helper function like assert_no_audit_entries(audit_dir: &TempDir), similar to how read_single_audit_entry was already extracted as a helper for the positive case.

Prompt for agents
In src/adapter/mod.rs, in the test module (around line 1149 where read_single_audit_entry is defined), add a helper function:

fn assert_no_audit_entries(audit_dir: &TempDir) {
    let entries: Vec<_> = std::fs::read_dir(audit_dir.path()).unwrap().collect();
    assert!(entries.is_empty(), "expected no audit log files, but found {}", entries.len());
}

Then replace the 3 occurrences of the duplicated assertion chain:
1. Lines 1188-1192 in audit_log_not_written_for_non_auditable_endpoint
2. Lines 1209-1213 in audit_log_not_written_in_dry_run_mode
3. Lines 1230-1234 in audit_log_not_written_when_disabled

Each should be replaced with: assert_no_audit_entries(&audit_dir);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Replace inline assertion patterns with the extracted helper function
for consistency and reduced duplication.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fohte fohte merged commit d1e7fad into fohte/audit-log Mar 12, 2026
7 checks passed
@fohte fohte deleted the fohte/impl-runok-audit-log-adapter branch March 12, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant