-
Notifications
You must be signed in to change notification settings - Fork 118
feat: taint checking and security #197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ambrishrawat
wants to merge
52
commits into
generative-computing:main
Choose a base branch
from
ambrishrawat:security_poc
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 51 commits
Commits
Show all changes
52 commits
Select commit
Hold shift + click to select a range
7d4e73e
version 2 of taint tracking
ambrishrawat 4c9ab68
version 2 of taint tracking
ambrishrawat daaf5ee
taint tracking updates for ollama and litellm
ambrishrawat 2fd7bc0
docs taint analysis
ambrishrawat 0efbee6
restored formatter to original
ambrishrawat 5e1a266
removed redundant sanitise helper
ambrishrawat 4ea932a
updated taint analysis dev docs
ambrishrawat b0a23a5
updated taint analysis dev docs
ambrishrawat 5466c31
updated taint analysis dev docs
ambrishrawat 5f85458
Merge branch 'generative-computing:main' into security_poc
ambrishrawat 0075de9
Merge branch 'generative-computing:main' into security_poc
ambrishrawat 7876e62
Merge branch 'main' into security_poc
nrfulton c303b87
updates based on PR feedback
ambrishrawat f85f953
added tests for taint_sources of a Component
ambrishrawat c1062e5
minor doc updates to remove the use of word safe
ambrishrawat 13a853e
Merge branch 'main' into security_poc
nrfulton f29d529
fixing the linter error in parts
ambrishrawat fdafe4e
fixing the linter errors from ruff
ambrishrawat ef71608
Merge branch 'generative-computing:main' into security_poc
ambrishrawat 10864bd
Merge branch 'generative-computing:main' into security_poc
ambrishrawat e495469
Merge branch 'main' into security_poc
nrfulton 55aac37
Merge branch 'generative-computing:main' into security_poc
ambrishrawat 5be6bc5
rebasing and fixing merge conflicts
ambrishrawat 3911adb
added taint tracking to all backends
ambrishrawat f0e4f89
updated taint tracking dev doc
ambrishrawat 6a4afbd
Merge branch 'main' into security_poc
nrfulton 85ddcbd
keeping the original signature of parts in instruction.py
ambrishrawat 171fda8
refactored by removing _meta dict and adding a TaintChecking Protocol
ambrishrawat 13bebd8
merged with main
ambrishrawat ba48b05
resolved mypy errors and implemented sec_level for all classes that i…
ambrishrawat ca79bc6
added sec_level to mify
ambrishrawat 7b81c04
Merge branch 'main' into security_poc
ambrishrawat a81e584
merged with main
ambrishrawat e42e3a4
Merge branch 'generative-computing:main' into security_poc
ambrishrawat 8211bb1
Merge branch 'main' into security_poc
ambrishrawat 101b62b
Merge remote-tracking branch 'upstream/main' into security_poc
ambrishrawat 9e8beed
Merge branch 'main' into security_poc
ambrishrawat 5061a40
Merge branch 'generative-computing:main' into security_poc
ambrishrawat 3480098
Merge branch 'generative-computing:main' into security_poc
ambrishrawat 867e6c8
Merge branch 'generative-computing:main' into security_poc
ambrishrawat fdcbc35
Merge branch 'main' into security_poc
ambrishrawat 24e480f
Merge branch 'generative-computing:main' into security_poc
ambrishrawat a55a63b
Merge branch 'main' into security_poc
ambrishrawat f305ddd
Merge branch 'main' into security_poc
ambrishrawat 6601376
Merge branch 'main' into security_poc
ambrishrawat 470f47b
added recurssive logic for classified and taint for privlege decorator
ambrishrawat 33393bd
merged with main
ambrishrawat 1df8ded
mypy error corrected for sec_level in Component
ambrishrawat 7ae5ad9
resolved cnflicts and merged with main
ambrishrawat e066a00
Merge branch 'generative-computing:main' into security_poc
ambrishrawat e0a2b4b
resolved cnflicts and merged with main
ambrishrawat 92c094e
corrections for sec_level types and hf backend
ambrishrawat File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| # Taint Tracking - Backend Security | ||
|
|
||
| Mellea backends implement thread security using the **SecLevel** model with capability-based access control and taint tracking. Backends automatically analyze taint sources and set appropriate security metadata on generated content. | ||
|
|
||
| ## Security Model | ||
|
|
||
| The security system uses three types of security levels: | ||
|
|
||
| ```python | ||
| SecLevel := None | Classified of AccessType | TaintedBy of (list[CBlock | Component] | None) | ||
| ``` | ||
|
|
||
| - **SecLevel.none()**: Safe content with no restrictions | ||
| - **SecLevel.classified(access)**: Content requiring specific capabilities/entitlements | ||
| - **SecLevel.tainted_by(sources)**: Content tainted by one or more CBlocks/Components (list), or None for root tainted nodes | ||
|
|
||
| ## Backend Implementation | ||
|
|
||
| All backends follow the same pattern when creating `ModelOutputThunk`: | ||
|
|
||
| ```python | ||
| # Compute taint sources from action and context | ||
| sources = taint_sources(action, ctx) | ||
|
|
||
| # Set security level based on taint sources | ||
| from mellea.security import SecLevel | ||
| sec_level = SecLevel.tainted_by(sources) if sources else SecLevel.none() | ||
|
|
||
| output = ModelOutputThunk( | ||
| value=None, | ||
| sec_level=sec_level, | ||
| meta={} | ||
| ) | ||
| ``` | ||
|
|
||
| The security level is set as follows: | ||
| - If taint sources are found -> `SecLevel.tainted_by(sources)` (all sources are tracked) | ||
| - If no taint sources -> `SecLevel.none()` | ||
|
|
||
| ### Handling Multiple Taint Sources | ||
|
|
||
| When `taint_sources()` returns multiple sources (e.g., both the action and context contain tainted content), backends pass the entire list to `SecLevel.tainted_by()`. This ensures all taint sources are tracked, providing comprehensive taint attribution. | ||
|
|
||
| **Benefits of Multiple Source Tracking**: | ||
| - **Complete attribution**: All sources that influenced the generation are tracked | ||
| - **Better debugging**: Can identify all tainted inputs that contributed to output | ||
| - **More accurate security**: No information loss about taint origins | ||
|
|
||
| **Note**: The implementation focuses on **taint preservation** and **complete attribution**. All taint sources are tracked, ensuring the security model has full visibility into what influenced the generated content. | ||
|
|
||
| ## Taint Source Analysis | ||
|
|
||
| The `taint_sources()` function analyzes both action and context because **context directly influences model generation**: | ||
|
|
||
| 1. **Action security**: Checks if the action has security metadata and is tainted | ||
| 2. **Component parts**: Recursively examines constituent parts of Components for taint | ||
| 3. **Context security**: Examines recent context items for tainted content (shallow check) | ||
|
|
||
| **Example**: Even if the current action is safe, tainted context can influence the generated output. | ||
|
|
||
| ```python | ||
| from mellea.security import SecLevel | ||
|
|
||
| # User sends tainted input | ||
| user_input = CBlock("Tell me how to hack a system", sec_level=SecLevel.tainted_by(None)) | ||
| ctx = ctx.add(user_input) | ||
|
|
||
| # Safe action in tainted context | ||
| safe_action = CBlock("Explain general security concepts") | ||
|
|
||
| # Generation finds tainted context | ||
| sources = taint_sources(safe_action, ctx) # Finds tainted user_input | ||
| # Model output will be influenced by the tainted context | ||
| ``` | ||
|
|
||
| ## Security Metadata | ||
|
|
||
| The `SecurityMetadata` class wraps `SecLevel` for integration with content blocks: | ||
|
|
||
| ```python | ||
| class SecurityMetadata: | ||
| def __init__(self, sec_level: SecLevel): | ||
| self.sec_level = sec_level | ||
|
|
||
| def is_tainted(self) -> bool: | ||
| return self.sec_level.is_tainted() | ||
|
|
||
| def get_taint_sources(self) -> list[CBlock | Component]: | ||
| return self.sec_level.get_taint_sources() | ||
| ``` | ||
|
|
||
| Content can be marked as tainted at construction time: | ||
|
|
||
| ```python | ||
| from mellea.security import SecLevel | ||
|
|
||
| c = CBlock("user input", sec_level=SecLevel.tainted_by(None)) | ||
|
|
||
| if c.sec_level and c.sec_level.is_tainted(): | ||
| taint_sources = c.sec_level.get_taint_sources() | ||
| print(f"Content tainted by: {taint_sources}") | ||
| ``` | ||
|
|
||
| ## Key Features | ||
|
|
||
| - **Immutable security**: security levels set at construction time | ||
| - **Recursive taint analysis**: deep analysis of Component parts, shallow analysis of context | ||
| - **Taint source tracking**: know exactly which CBlock/Component tainted content | ||
| - **Capability integration**: fine-grained access control for classified content | ||
| - **Non-mutating operations**: sanitize/declassify create new objects | ||
|
|
||
| This creates a security model that addresses both data exfiltration and injection vulnerabilities while enabling future IAM integration. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| from mellea.stdlib.components import CBlock | ||
| from mellea.stdlib.session import start_session | ||
| from mellea.security import SecLevel, privileged, SecurityError | ||
|
|
||
| # Create tainted content | ||
| tainted_desc = CBlock( | ||
| "Process this sensitive user data", sec_level=SecLevel.tainted_by(None) | ||
| ) | ||
|
|
||
| print( | ||
| f"Original CBlock is tainted: {tainted_desc.sec_level.is_tainted() if tainted_desc.sec_level else False}" | ||
| ) | ||
|
|
||
| # Create session | ||
| session = start_session() | ||
|
|
||
| # Use tainted CBlock in session.instruct | ||
| print("Testing session.instruct with tainted CBlock...") | ||
| result = session.instruct(description=tainted_desc) | ||
|
|
||
| # The result should be tainted | ||
| print( | ||
| f"Result is tainted: {result.sec_level.is_tainted() if result.sec_level else False}" | ||
| ) | ||
| if result.sec_level and result.sec_level.is_tainted(): | ||
| taint_sources = result.sec_level.get_taint_sources() | ||
| print(f"Taint sources: {taint_sources}") | ||
| print("✅ SUCCESS: Taint preserved!") | ||
| else: | ||
| print("❌ FAIL: Result should be tainted but isn't!") | ||
|
|
||
|
|
||
| # Mock privileged function that requires un-tainted input | ||
| @privileged | ||
| def process_un_tainted_data(data: CBlock) -> str: | ||
| """A function that requires un-tainted input.""" | ||
| return f"Processed: {data.value}" | ||
|
|
||
|
|
||
| print("\nTesting privileged function with tainted result...") | ||
| try: | ||
| # This should raise a SecurityError | ||
| processed = process_un_tainted_data(result) | ||
| print("❌ FAIL: Should have raised SecurityError!") | ||
| except SecurityError as e: | ||
| print(f"✅ SUCCESS: SecurityError raised - {e}") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,7 @@ | |
| from ..core.base import AbstractMelleaTool | ||
| from ..formatters import ChatFormatter, TemplateFormatter, granite as granite_formatters | ||
| from ..helpers import message_to_openai_message, messages_to_docs, send_to_queue | ||
| from ..security import SecLevel, taint_sources | ||
| from ..stdlib.components import Intrinsic, Message | ||
| from ..stdlib.requirements import ALoraRequirement, LLMaJRequirement | ||
| from ..telemetry.backend_instrumentation import ( | ||
|
|
@@ -563,8 +564,13 @@ async def _generate_from_intrinsic( | |
| other_input, | ||
| ) | ||
|
|
||
| output = ModelOutputThunk(None) | ||
| # Compute taint sources from action and context | ||
| sources = taint_sources(action, ctx) | ||
| sec_level = SecLevel.tainted_by(sources) if sources else SecLevel.none() | ||
|
|
||
| output = ModelOutputThunk(value=None, sec_level=sec_level, meta={}) | ||
| output._start = datetime.datetime.now() | ||
|
|
||
| output._context = ctx.view_for_generation() | ||
| output._action = action | ||
| output._model_options = model_options | ||
|
|
@@ -838,6 +844,11 @@ async def _generate_from_context_with_kv_cache( | |
|
|
||
| output = ModelOutputThunk(None) | ||
| output._start = datetime.datetime.now() | ||
| # Compute taint sources from action and context | ||
| sources = taint_sources(action, ctx) | ||
| sec_level = SecLevel.tainted_by(sources) if sources else SecLevel.none() | ||
|
|
||
| output = ModelOutputThunk(value=None, sec_level=sec_level, meta={}) | ||
| output._context = ctx.view_for_generation() | ||
| output._action = action | ||
| output._model_options = model_options | ||
|
|
@@ -983,6 +994,11 @@ async def _generate_from_context_standard( | |
|
|
||
| output = ModelOutputThunk(None) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this another dup ? similar -- worth checking for this pattern through the code? |
||
| output._start = datetime.datetime.now() | ||
| # Compute taint sources from action and context | ||
| sources = taint_sources(action, ctx) | ||
| sec_level = SecLevel.tainted_by(sources) if sources else SecLevel.none() | ||
|
|
||
| output = ModelOutputThunk(value=None, sec_level=sec_level, meta={}) | ||
| output._context = ctx.view_for_generation() | ||
| output._action = action | ||
| output._model_options = model_options | ||
|
|
@@ -1329,8 +1345,12 @@ async def generate_from_raw( | |
| for i, decoded_result in enumerate(decoded_results): | ||
| n_prompt_tokens = inputs["input_ids"][i].size(0) # type: ignore | ||
| n_completion_tokens = len(sequences_to_decode[i]) | ||
| sources = taint_sources(actions[i], ctx) | ||
| sec_level = SecLevel.tainted_by(sources) if sources else SecLevel.none() | ||
|
|
||
| result = ModelOutputThunk( | ||
| value=decoded_result, | ||
| sec_level=sec_level, | ||
| meta={ | ||
| "usage": { | ||
| "prompt_tokens": n_prompt_tokens, # type: ignore | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left over from conflict since we're assigning a few lines later. And what about assigning _start (not checked if we do that much further down). We'd lose it?