File size: 3,244 Bytes
ccb1bbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
"""
security.py
-----------
Implements the two security controls we demonstrate for the capstone:

1. NON-INTERACTIVE ACCESS
   No agent ever fetches or renders raw third-party HTML. Every agent talks
   only to structured, official APIs (Gemini, YouTube Data API, Semantic
   Scholar, Google Books). This removes the indirect-prompt-injection
   surface that comes from an agent reading arbitrary web pages.

2. OUTPUT CONTENT SAFETY GATE (Governance / Logic Review)
   Before the final report is returned to the user, the Aggregator Agent's
   output passes through a lightweight safety classifier. If the topic or
   generated content falls into a disallowed category (e.g. weapons,
   self-harm instructions, hate content), the report is blocked and a safe
   message is returned instead — mirroring the "Vibe Diff" / mandatory
   logic-review idea from the security framework: nothing ships without a
   check, even though the check here is automated rather than a human.
"""

import re

# Minimal, fast keyword-based pre-filter. This runs before the LLM-based
# check so obviously disallowed topics are caught without spending tokens.
_BLOCKED_PATTERNS = [
    r"\bmake\s+a\s+bomb\b",
    r"\bsynthesiz(e|ing)\s+(nerve agent|sarin|ricin)\b",
    r"\bhow to (kill|murder)\b",
    r"\bchild\s+sexual\b",
]


def keyword_prefilter(topic: str) -> bool:
    """Returns True if topic is safe to proceed, False if blocked outright."""
    lowered = topic.lower()
    for pattern in _BLOCKED_PATTERNS:
        if re.search(pattern, lowered):
            return False
    return True


def llm_safety_check(gemini_model, final_report_text: str) -> tuple[bool, str]:
    """
    Uses Gemini itself as a lightweight judge over the FINAL aggregated
    report (not the raw topic) to catch anything that slipped through.

    Returns (is_safe, reason).
    """
    prompt = (
        "You are a content safety classifier. Read the study report below "
        "and answer with exactly one word on the first line: SAFE or UNSAFE. "
        "On the second line, give a one-sentence reason. "
        "Mark UNSAFE only if the content provides actionable instructions "
        "for violence, weapons, self-harm, or clearly illegal harmful acts. "
        "Educational/academic framing of sensitive topics is SAFE.\n\n"
        f"REPORT:\n{final_report_text[:4000]}"
    )
    try:
        response = gemini_model["client"].models.generate_content(
            model=gemini_model["model_name"], contents=prompt
        )
        text = response.text.strip()
        first_line = text.splitlines()[0].strip().upper()
        reason = text.splitlines()[1] if len(text.splitlines()) > 1 else ""
        is_safe = first_line.startswith("SAFE")
        return is_safe, reason
    except Exception as e:
        # If the check fails because of a 429 rate limit / quota exhaustion, bypass rather than block the whole application.
        if "429" in str(e) or "RESOURCE_EXHAUSTED" in str(e) or "quota" in str(e).lower():
            return True, f"Bypassed (API rate limit / quota exhausted)"
        # Fail safe for other errors: if the classifier itself errors, don't silently ship unchecked content
        return False, f"Safety check failed to run: {e}"