Repository navigation
feat(cyber-guardian-agent): migrate recipe to contrib/python and align with repo standards - #2776
abhilash-adavi wants to merge 9 commits into
Conversation
…n with repo standards - Move cyber-guardian-agent recipe to contrib/python/cyber-guardian-agent with app/ package layout - Add manifest.yaml with ownership, architecture, dependencies, and tags metadata - Add Dockerfile, .dockerignore, and runnability test suite (tests/test_runnability.py, tests/conftest.py) - Upgrade model to gemini-3.5-flash read via MODEL_NAME from environment with load_dotenv() bootstrap - Remove import-time BigQuery table initialization and ADC lookup in favor of lazy runtime initialization
There was a problem hiding this comment.
Automated House Rules review — 1 finding(s).
Also, on lines this PR does not change:
contrib/python/cyber-guardian-agent/app/tools.py:1— [MINOR] the .py files carry two different headers: 0 a shorter notice, 16 none, and 2 the full Apache block (contrib/python/cyber-guardian-agent/app/tools.py; and 5 more)
There was a problem hiding this comment.
Automated Correctness review — 0 finding(s).
Also, on lines this PR does not change:
contrib/python/cyber-guardian-agent/app/tools.py:111— [MAJOR] Ifasset_rowsis empty because the hostname was not found, the subsequent return statement accessingasset_rows[0]will raise anIndexError. Can we check ifasset_rowsis non-empty before accessing its elements?
Round 1 · 0 of this PR's 25 automated comments used · this round is capped at 20 across all reviewers.
|
This pull request isn't ready for review yet. I removed the review request for Failing checks (1) Fix the failing checks. Once everything is green I will re-request the review automatically, so there is no need to ping anyone. This is re-checked every 15 minutes. Applied by the review gate ( |
| client.query(context_query, job_config=job_config).result() | ||
| ) | ||
| asset_context = ( | ||
| dict(asset_rows[0]) if asset_rows else "No asset context found." |
There was a problem hiding this comment.
[MAJOR] It seems the fallback path here returns a string while the success path returns a dictionary. Can we return an empty dictionary instead to keep the JSON schema consistent?
There was a problem hiding this comment.
Fixed in b5e8918 — updated the fallback in triageQueryTool to return {} when asset_rows is empty so asset_context is consistently a dictionary.
happyhuman
left a comment
There was a problem hiding this comment.
Just reviewed the PR and left a few comments. PTAL
| * **`network_connection_log`**: | ||
| * **Schema:** `log_timestamp, source_host, source_ip, destination_ip, destination_port, protocol, process_pid_ref` | ||
| * **Purpose:** Correlate network egress for EDR, confirm `SourceHost` to `DestinationIP` for IOC_MATCH. | ||
| * *(Implicit `mail_logs` for PHISHING_EMAIL - assumed schema for sender, recipient, URL, SMTP relay)* |
There was a problem hiding this comment.
Where does mail_logs come from? I only see six CSVs in sample_data/ and none of them is mail logs.
There was a problem hiding this comment.
Fixed in 036cc4d — removed the non-existent mail_logs reference and updated the PHISHING_EMAIL step to analyze extracted email entities and correlate with network_connection_log.
| 4. **Construct Output and Transfer:** | ||
| * Create the JSON object containing the `recommended_actions` list. | ||
| * Return the JSON object. | ||
| * *(Note: `responseExecutionTool` is used by the Orchestrator *after* HITL approval for actual (simulated) execution, not by the Response Agent directly in this prompting context.)* |
There was a problem hiding this comment.
This note says responseExecutionTool isn't used by the Response Agent, but it's listed under Tools above. Which one is right?
There was a problem hiding this comment.
Fixed in 036cc4d — removed the contradictory note and updated Step 3 to clarify that response_agent calls responseExecutionTool for auto-approved actions (requires_approval: false) while deferring requires_approval: true actions for HITL approval.
| If classification is: IOC-heavy [IOC_MATCH, PHISHING_EMAIL] | ||
| Do NOT output intermediate conversational messages or wait for user confirmation before transferring. Perform all agent transfers immediately and autonomously. | ||
| Always follow this path "CALL threatintel_agent FIRST AND THEN CALL investigation_agent" with the below instructions | ||
| Delegate to Threat Intel (threat_intel_agent): Call threatintel_agent(indicators=list_of_extracted_iocs). |
There was a problem hiding this comment.
uses both threat_intel_agent and threatintel_agent here
There was a problem hiding this comment.
Fixed in 036cc4d — standardized all references in app/prompt.py to threat_intel_agent.
|
|
||
| **Input:** | ||
| * `indicators`: A list of strings, where each string is an IOC value (e.g., IP address, hash, URL) (list[str]). | ||
| Note: Always send the input to the tool as a list even if there is only 1 IOC value or Empty string if there are no IOC values. |
There was a problem hiding this comment.
Should this be an empty list rather than an empty string? indicators is typed list[str] just above.
There was a problem hiding this comment.
Fixed in 036cc4d — updated the note to specify an empty list ([]) instead of an empty string.
| rows = list(client.query(query, job_config=job_config).result()) | ||
| return bq_rows_to_json(rows) | ||
|
|
||
| return "Query did not match a known investigation type. Please provide more specific parameters." |
There was a problem hiding this comment.
Is there a reason this branch returns a plain string? The other two return JSON.
There was a problem hiding this comment.
Fixed in 036cc4d — wrapped the fallback error message in json.dumps({"error": ...}) so all branches return JSON.
|
|
||
| # --- Configure Logging --- | ||
| logging.basicConfig(level=logging.INFO) | ||
| logger = logging.getLogger(__name__) |
There was a problem hiding this comment.
logger isn't used in this file
There was a problem hiding this comment.
Fixed in 036cc4d — removed the unused logging import and logger variable.
| Step 2: Delegate to Triage (Always First) | ||
| Action: Call the triage_agent. | ||
| Agent Call: triage_agent(hostname=extracted_hostname, alert_type=classification) | ||
| Critical Check: If the triage_agent returns is_duplicate: true, you MUST stop all further processing. Log this result and terminate the workflow., |
There was a problem hiding this comment.
stray comma after workflow.
There was a problem hiding this comment.
Fixed in 036cc4d — removed the stray comma after workflow..
| version = "0.1.0" | ||
| description = "AI-driven agent for cybersecurity incident response" | ||
| authors = [ | ||
| { name = "Cyber Guardian Team" }, |
There was a problem hiding this comment.
Can you please put real names here? Cyber Guardian Team and Google POC look like placeholders.
There was a problem hiding this comment.
Fixed in 036cc4d — updated authors to Abhilash Adavi and Anas Aslam.
| * `action_command`: Value from `ActionCommand`. | ||
| * `target`: Value from `TargetDescription`. | ||
| * `requires_approval`: Value from `RequiresApproval`. | ||
| * If `requires_approval` is `false`, call `responseExecutionTool(action=action_command, target=target)` to simulate immediate execution. If `requires_approval` is `true`, do not execute it until Human-In-The-Loop (HITL) approval is granted. |
There was a problem hiding this comment.
[MAJOR] Is there a reason we are allowing the response agent to autonomously execute actions without Human-In-The-Loop approval when requires_approval is false? This could lead to unauthorized tool execution if the triggering inputs are manipulated.
Summary
Migrates the
cyber-guardian-agentrecipe frompython/agents/cyber-guardian-agenttocontrib/python/cyber-guardian-agentand updates it to meet all repository baseline standards.Changes
contrib/python/cyber-guardian-agentwith standardizedapp/package structure,Dockerfile, and.dockerignore.manifest.yamlwith ownership (team, POC, and contributors), architecture, dependencies, and tags.gemini-3.5-flashconfigured viaMODEL_NAMEin.env.examplewithload_dotenv()inapp/__init__.py(no hardcoded model literals in source).tests/test_runnability.pyandtests/conftest.py; formatted and linted withruff.Verification
uv run python tools/validate.py all contrib/python/cyber-guardian-agent— Passeduv run ruff check && uv run ruff format --check— Passeduv run --directory contrib/python/cyber-guardian-agent pytest -v— Passedgemini-3.5-flash) and BigQuery.