Skip to content

[rust-guard] Rust Guard: Add tests for check_file_secrecy + Arc-wrap PolicyContext #5143

@github-actions

Description

@github-actions

🦀 Rust Guard Improvement Report

Improvement 1: Add Unit Tests for check_file_secrecy

Category: Test Coverage
File(s): guards/github-guard/rust-guard/src/labels/tool_rules.rs
Effort: Small (< 15 min)
Risk: Low

Problem

check_file_secrecy (line 738) is the sole function deciding whether a get_file_contents request reveals private file paths (.env, workflow files, secrets.json, etc.). It is called from the get_file_contents arm of apply_tool_labels (line 390) and directly influences secrecy labels in production. Yet tool_rules.rs has zero unit tests across its 772 lines.

A typo in a pattern or a mis-ordered ends_with check could silently stop enforcing secrecy on sensitive files with no test catching it.

Suggested Change

Add a #[cfg(test)] module inside tool_rules.rs with targeted tests for check_file_secrecy:

Before

// tool_rules.rs — no test module exists

After

#[cfg(test)]
mod tests {
    use super::*;
    use crate::labels::helpers::PolicyContext;

    fn ctx() -> PolicyContext { PolicyContext::default() }
    fn private_secrecy(owner: &str, repo: &str, repo_id: &str) -> Vec<String> {
        policy_private_scope_label(owner, repo, repo_id, &ctx())
    }

    #[test]
    fn check_file_secrecy_env_file_triggers_private() {
        let result = check_file_secrecy(".env", vec![], "octocat", "hello-world", "octocat/hello-world", &ctx());
        assert_eq!(result, private_secrecy("octocat", "hello-world", "octocat/hello-world"));
    }

    #[test]
    fn check_file_secrecy_workflow_file_triggers_private() {
        let result = check_file_secrecy(".github/workflows/ci.yml", vec![], "octocat", "hello-world", "octocat/hello-world", &ctx());
        assert_eq!(result, private_secrecy("octocat", "hello-world", "octocat/hello-world"));
    }

    #[test]
    fn check_file_secrecy_secrets_json_triggers_private() {
        let result = check_file_secrecy("config/secrets.json", vec![], "octocat", "hello-world", "octocat/hello-world", &ctx());
        assert_eq!(result, private_secrecy("octocat", "hello-world", "octocat/hello-world"));
    }

    #[test]
    fn check_file_secrecy_normal_source_file_keeps_default() {
        let default = vec!["private:octocat/hello-world".to_string()];
        let result = check_file_secrecy("src/main.rs", default.clone(), "octocat", "hello-world", "octocat/hello-world", &ctx());
        assert_eq!(result, default);
    }

    #[test]
    fn check_file_secrecy_dotenv_extension_triggers_private() {
        // file named "config.env" or path ending in ".env"
        let result = check_file_secrecy("deploy/config.env", vec![], "octocat", "hello-world", "octocat/hello-world", &ctx());
        assert_eq!(result, private_secrecy("octocat", "hello-world", "octocat/hello-world"));
    }
}

Why This Matters

check_file_secrecy is a security gate. Without tests, any future refactor (e.g., adjusting SENSITIVE_FILE_PATTERNS or SENSITIVE_FILE_KEYWORDS in constants.rs) could silently break enforcement and expose private file contents with no CI signal. These tests take under 15 minutes to write and provide an immediate safety net.


Improvement 2: Wrap RUNTIME_POLICY_CONTEXT in Arc<PolicyContext>

Category: Performance
File(s): guards/github-guard/rust-guard/src/lib.rs
Effort: Small (< 15 min)
Risk: Low

Problem

get_runtime_policy_context() (line 56) is called at the start of every label_resource (line 651) and label_response (line 815) invocation. It currently deep-clones the stored PolicyContext from the Mutex:

fn get_runtime_policy_context() -> PolicyContext {
    RUNTIME_POLICY_CONTEXT
        .lock()
        .ok()
        .and_then(|guard| guard.clone())  // ← deep-clone
        .unwrap_or_default()
}

PolicyContext contains 6 Vec<String> fields (scopes, trusted_bots, blocked_users, approval_labels, trusted_users, endorsement_reactions, disapproval_reactions) and 4 plain String fields. Each call allocates and copies all of these.

In a production WASM guard, every tool call triggers at least one label_resource + one label_response, so this clone runs at least twice per incoming MCP request.

Suggested Change

Store Arc<PolicyContext> in the Mutex so get_runtime_policy_context only bumps an atomic refcount:

Before

static RUNTIME_POLICY_CONTEXT: Mutex<Option<PolicyContext>> = Mutex::new(None);

fn get_runtime_policy_context() -> PolicyContext {
    RUNTIME_POLICY_CONTEXT
        .lock()
        .ok()
        .and_then(|guard| guard.clone())
        .unwrap_or_default()
}

fn set_runtime_policy_context(ctx: PolicyContext) {
    if let Ok(mut guard) = RUNTIME_POLICY_CONTEXT.lock() {
        *guard = Some(ctx);
    }
}

After

use std::sync::Arc;

static RUNTIME_POLICY_CONTEXT: Mutex<Option<Arc<PolicyContext>>> = Mutex::new(None);

fn get_runtime_policy_context() -> Arc<PolicyContext> {
    RUNTIME_POLICY_CONTEXT
        .lock()
        .ok()
        .and_then(|guard| guard.clone())       // clones Arc (atomic refcount)
        .unwrap_or_else(|| Arc::new(PolicyContext::default()))
}

fn set_runtime_policy_context(ctx: PolicyContext) {
    if let Ok(mut guard) = RUNTIME_POLICY_CONTEXT.lock() {
        *guard = Some(Arc::new(ctx));
    }
}

All existing callers (label_resource, label_response) assign let ctx = get_runtime_policy_context() and pass &ctx to downstream functions. Since Arc<T> implements Deref<Target = T>, &ctx continues to coerce to &PolicyContextno caller changes required.

Why This Matters

Eliminates 7+ heap allocations and memcpys of Vec contents on every guard invocation, replaced by a single AtomicUsize increment. In a WASM-constrained environment where allocator overhead is higher than on native, this is a measurable win — especially for agents calling many tools in sequence (each call re-invokes the guard).


Codebase Health Summary

  • Total Rust files: 10
  • Total lines: ~13,544
  • Areas analyzed: lib.rs, labels/mod.rs, labels/helpers.rs, labels/tool_rules.rs, labels/response_items.rs, labels/response_paths.rs, labels/backend.rs, labels/constants.rs, tools.rs
  • Areas with no further improvements: tools.rs (tests added, BLOCKED_TOOLS const added)
  • Critical gap remaining: tool_rules.rs and response_paths.rs still have 0 unit tests

Generated by Rust Guard Improver • Run: §25369159959

Generated by Rust Guard Improver · ● 2.4M ·

  • expires on May 12, 2026, 9:51 AM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    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