From 86ea3540e3b0f0d43ad012c208a252b2e239bc27 Mon Sep 17 00:00:00 2001 From: Mick Grove Date: Tue, 29 Jul 2025 20:20:33 -0700 Subject: [PATCH] Added support for Slack. Wrote a basic integration test --- README.md | 4 +- tests/int_slack.rs | 198 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 tests/int_slack.rs diff --git a/README.md b/README.md index 5ade809..09fabc1 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ Kingfisher originated as a fork of Praetorian's [Nosey Parker](https://github.co ## What Kingfisher Adds - **Live validation** via cloud-provider APIs -- **Language-aware detection** (AST parsing) for ~20 languages +- **Language-aware detection** (source-code parsing) for ~20 languages - **Extra targets**: GitLab repos, Docker images, Jira issues, and Slack messages - **Baseline mode**: ignore known secrets, flag only new ones -- **Native Windows** binaries +- **Native Windows** binary ## Key Features diff --git a/tests/int_slack.rs b/tests/int_slack.rs new file mode 100644 index 0000000..d238bce --- /dev/null +++ b/tests/int_slack.rs @@ -0,0 +1,198 @@ +use std::{ + env, + sync::{Arc, Mutex}, +}; + +use anyhow::Result; +use kingfisher::{ + cli::{ + commands::{ + github::{GitCloneMode, GitHistoryMode, GitHubRepoType}, + gitlab::GitLabRepoType, + inputs::{ContentFilteringArgs, InputSpecifierArgs}, + output::{OutputArgs, ReportOutputFormat}, + rules::RuleSpecifierArgs, + scan::{ConfidenceLevel, ScanArgs}, + }, + global::{AdvancedArgs, Mode}, + GlobalArgs, + }, + findings_store::FindingsStore, + rule_loader::RuleLoader, + rules_database::RulesDatabase, + scanner::run_async_scan, +}; +use tempfile::TempDir; +use url::Url; +use wiremock::{ + matchers::{method, path}, + Mock, MockServer, ResponseTemplate, +}; + +struct TestContext { + rules_db: Arc, +} + +impl TestContext { + fn new() -> Result { + let scan_args = ScanArgs { + num_jobs: 2, + rules: RuleSpecifierArgs { + rules_path: Vec::new(), + rule: vec!["all".into()], + load_builtins: true, + }, + input_specifier_args: InputSpecifierArgs { + path_inputs: Vec::new(), + git_url: Vec::new(), + github_user: Vec::new(), + github_organization: Vec::new(), + all_github_organizations: false, + github_api_url: Url::parse("https://api.github.com/").unwrap(), + github_repo_type: GitHubRepoType::Source, + gitlab_user: Vec::new(), + gitlab_group: Vec::new(), + all_gitlab_groups: false, + gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), + gitlab_repo_type: GitLabRepoType::Owner, + jira_url: None, + jql: None, + slack_query: None, + slack_api_url: Url::parse("https://slack.com/api/").unwrap(), + max_results: 10, + docker_image: Vec::new(), + git_clone: GitCloneMode::Bare, + git_history: GitHistoryMode::Full, + scan_nested_repos: true, + commit_metadata: true, + }, + content_filtering_args: ContentFilteringArgs { + max_file_size_mb: 25.0, + extraction_depth: 2, + no_binary: true, + no_extract_archives: false, + exclude: Vec::new(), + }, + confidence: ConfidenceLevel::Low, + no_validate: true, + rule_stats: false, + only_valid: false, + min_entropy: Some(0.0), + redact: false, + git_repo_timeout: 1800, + output_args: OutputArgs { output: None, format: ReportOutputFormat::Pretty }, + no_dedup: true, + snippet_length: 128, + baseline_file: None, + manage_baseline: false, + }; + + let loaded = RuleLoader::from_rule_specifiers(&scan_args.rules).load(&scan_args)?; + let resolved = loaded.resolve_enabled_rules()?; + let rules_db = RulesDatabase::from_rules(resolved.into_iter().cloned().collect())?; + Ok(Self { rules_db: Arc::new(rules_db) }) + } +} + +#[tokio::test] +async fn test_scan_slack_messages() -> Result<()> { + let ctx = TestContext::new()?; + + let server = MockServer::start().await; + let response = serde_json::json!({ + "ok": true, + "messages": { + "matches": [{ + "permalink": "https://example.slack.com/archives/C123/p1234", + "text": "This contains a github token ghp_1wuHFikBKQtCcH3EB2FBUkyn8krXhP2qLqPa", + "ts": "1234.56", + "channel": {"id": "C123", "name": "general"} + }], + "pagination": {"page": 1, "page_count": 1} + } + }); + Mock::given(method("GET")) + .and(path("/search.messages")) + .respond_with(ResponseTemplate::new(200).set_body_json(response)) + .mount(&server) + .await; + + env::set_var("KF_SLACK_TOKEN", "xoxp-test"); + + let temp_dir = TempDir::new()?; + let clone_dir = temp_dir.path().to_path_buf(); + + let scan_args = ScanArgs { + num_jobs: 2, + rules: RuleSpecifierArgs { + rules_path: Vec::new(), + rule: vec!["all".into()], + load_builtins: true, + }, + input_specifier_args: InputSpecifierArgs { + path_inputs: Vec::new(), + git_url: Vec::new(), + github_user: Vec::new(), + github_organization: Vec::new(), + all_github_organizations: false, + github_api_url: Url::parse("https://api.github.com/").unwrap(), + github_repo_type: GitHubRepoType::Source, + gitlab_user: Vec::new(), + gitlab_group: Vec::new(), + all_gitlab_groups: false, + gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), + gitlab_repo_type: GitLabRepoType::Owner, + jira_url: None, + jql: None, + slack_query: Some("test".into()), + slack_api_url: Url::parse(&format!("{}/", server.uri()))?, + max_results: 10, + docker_image: Vec::new(), + git_clone: GitCloneMode::Bare, + git_history: GitHistoryMode::Full, + scan_nested_repos: true, + commit_metadata: true, + }, + content_filtering_args: ContentFilteringArgs { + max_file_size_mb: 25.0, + extraction_depth: 2, + no_binary: true, + no_extract_archives: false, + exclude: Vec::new(), + }, + confidence: ConfidenceLevel::Low, + no_validate: true, + rule_stats: false, + only_valid: false, + min_entropy: Some(0.0), + redact: false, + git_repo_timeout: 1800, + output_args: OutputArgs { output: None, format: ReportOutputFormat::Pretty }, + no_dedup: true, + snippet_length: 128, + baseline_file: None, + manage_baseline: false, + }; + + let global_args = GlobalArgs { + verbose: 0, + quiet: true, + color: Mode::Auto, + no_update_check: false, + self_update: false, + progress: Mode::Never, + ignore_certs: false, + advanced: AdvancedArgs { rlimit_nofile: 16384 }, + }; + + let datastore = Arc::new(Mutex::new(FindingsStore::new(clone_dir))); + + run_async_scan(&global_args, &scan_args, Arc::clone(&datastore), &ctx.rules_db).await?; + + let findings = { + let ds = datastore.lock().unwrap(); + ds.get_matches().len() + }; + assert!(findings > 0); + Ok(()) +} \ No newline at end of file