Skip to content

feat(security): tool-call SecretProxy (placeholders + redact) - #94

Merged
divo12 merged 4 commits into
mainfrom
feat/secret-proxy
Aug 23, 2026
Merged

divo12 merged 4 commits into
mainfrom
feat/secret-proxy

Conversation

@divo12

@divo12 divo12 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

User description

Summary

  • Add dream.security.SecretProxy: register secrets → stable dream_secret_* placeholders, resolve at trusted tool execution, redact results
  • Wire optional EngineToolDispatcher.secret_proxy (PRE hooks still see placeholders; execute gets real values)
  • TDD + @pytest.mark.eval proving raw secrets never leak into tool results

Closes the Hermes/Paperclip gap where stacks only redacted or env-injected secrets rather than proxying them.

Test plan

  • uv run pytest tests/test_security tests/test_engine/test_secret_proxy_dispatch.py tests/evals/eval_secret_proxy.py
  • uv run ruff check / uv run mypy on touched modules
  • CI green on the PR

Made with Cursor


CodeAnt-AI Description

Keep registered secrets out of model-facing tool calls and results

What Changed

  • Registered secrets are represented by stable dream_secret_* placeholders in model inputs, transcripts, and pre-tool hooks
  • Tool execution receives the real secret only after dispatch, including permission checks that need the resolved value
  • Tool results replace raw secrets with placeholders in text and nested structured data before returning to the conversation or being retained
  • Secret protection can be enabled through the public harness and query-engine setup, while dispatchers without it keep their existing behavior

Impact

✅ Fewer secret leaks in tool results
✅ Safer transcripts and tool hooks
✅ Secrets remain available to trusted tools

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Keep opaque dream_secret_* placeholders in model/transcript context;
resolve only inside EngineToolDispatcher execute and redact tool output
before it returns to the conversation.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codeant-ai

codeant-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed 3892b69 Aug 23, 2026 · 07:14 07:15
✅ Reviewed your PR 1512b9c Aug 07, 2026 · 09:52 09:55

@codeant-ai

codeant-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Aug 7, 2026
Comment thread src/dream/engine/_tool_dispatch.py
Comment thread src/dream/engine/_tool_dispatch.py Outdated
Comment thread src/dream/engine/_tool_dispatch.py
divo12 and others added 2 commits August 7, 2026 10:50
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@divo12

divo12 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai please review

@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a public SecretProxy abstraction and wires placeholder resolution and result redaction through harness construction and tool dispatch.

  • Registers stable model-facing placeholders and deep-resolves them immediately before tool execution
  • Redacts textual, structured, and offloaded tool output
  • Adds dispatcher, factory, unit, and integration-style evaluation coverage

Confidence Score: 3/5

The PR should not merge until final hook replacements are redacted and multi-secret redaction preserves stable placeholders.

The new proxy can return raw secrets from POST_TOOL_USE replacement results and can corrupt generated placeholders during sequential multi-secret redaction.

Files Needing Attention: src/dream/engine/_tool_dispatch.py, src/dream/security/_secret_proxy.py

Security Review

A POST_TOOL_USE replacement is applied after the redaction pass, allowing hook-provided raw secrets to reach the model-facing result.

Important Files Changed

Filename Overview
src/dream/security/_secret_proxy.py Introduces registration, deep placeholder resolution, and redaction; sequential replacement can mutate placeholders inserted earlier in the same pass.
src/dream/engine/_tool_dispatch.py Resolves secrets for execution and redacts results before offload and hooks, but POST_TOOL_USE replacement text bypasses final redaction.
src/dream/_factory.py Threads the optional SecretProxy from the public harness factory into each session engine.
src/dream/engine/_engine.py Threads SecretProxy into the canonical EngineToolDispatcher construction path.
tests/test_engine/test_secret_proxy_dispatch.py Covers resolution, base-result redaction, PRE-hook visibility, permission effects, and structured results but not POST-hook replacements.
tests/test_security/test_secret_proxy.py Covers stable registration, nested transformations, and overlapping raw values but not raw values occurring inside generated placeholders.

Sequence Diagram

sequenceDiagram
    participant M as Model
    participant H as PRE hook
    participant D as Dispatcher
    participant P as SecretProxy
    participant T as Tool
    participant O as POST hook
    M->>D: placeholder-bearing input
    D->>H: placeholders
    H-->>D: input/replacement
    D->>P: resolve input
    P-->>D: raw secret values
    D->>T: execute with raw values
    T-->>D: tool result
    D->>P: redact result
    P-->>D: placeholder-bearing result
    D->>O: redacted summary
    O-->>D: optional replacement_result
    D-->>M: final result
Loading

Comments Outside Diff (2)

  1. src/dream/engine/_tool_dispatch.py, line 212-213 (link)

    P1 security Hook replacements bypass redaction

    When a POST_TOOL_USE hook returns a replacement containing a registered raw secret, the dispatcher applies it after the only redaction pass and returns it directly, exposing the secret in the model-facing transcript.

    How this was verified: The result path goes from _offload_and_record redaction through the later replacement_result assignment to the direct return.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/dream/engine/_tool_dispatch.py
    Line: 212-213
    
    Comment:
    **Hook replacements bypass redaction**
    
    When a `POST_TOOL_USE` hook returns a replacement containing a registered raw secret, the dispatcher applies it after the only redaction pass and returns it directly, exposing the secret in the model-facing transcript.
    
    **How this was verified:** The result path goes from `_offload_and_record` redaction through the later `replacement_result` assignment to the direct return.
    
    
    
    ---
    
    For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
  2. src/dream/security/_secret_proxy.py, line 1637-1639 (link)

    P1 Sequential redaction corrupts placeholders

    When one registered raw value occurs inside another secret's generated dream_secret_* placeholder, a later replacement rewrites the placeholder inserted by an earlier pass, causing redaction to return a corrupted placeholder instead of the stable value returned by register.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/dream/security/_secret_proxy.py
    Line: 1637-1639
    
    Comment:
    **Sequential redaction corrupts placeholders**
    
    When one registered raw value occurs inside another secret's generated `dream_secret_*` placeholder, a later replacement rewrites the placeholder inserted by an earlier pass, causing redaction to return a corrupted placeholder instead of the stable value returned by `register`.
    
    ---
    
    For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Prompt To Fix All With AI
### Issue 1
src/dream/engine/_tool_dispatch.py:212-213
**Hook replacements bypass redaction**

When a `POST_TOOL_USE` hook returns a replacement containing a registered raw secret, the dispatcher applies it after the only redaction pass and returns it directly, exposing the secret in the model-facing transcript.

**How this was verified:** The result path goes from `_offload_and_record` redaction through the later `replacement_result` assignment to the direct return.

```suggestion
        if post.replacement_result is not None:
            content = post.replacement_result
            if self.secret_proxy is not None:
                content = self.secret_proxy.redact_text(content)
```

### Issue 2
src/dream/security/_secret_proxy.py:1637-1639
**Sequential redaction corrupts placeholders**

When one registered raw value occurs inside another secret's generated `dream_secret_*` placeholder, a later replacement rewrites the placeholder inserted by an earlier pass, causing redaction to return a corrupted placeholder instead of the stable value returned by `register`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "refactor(security): type structured reda..." | Re-trigger Greptile

@divo12
divo12 merged commit a86ceb5 into main Aug 23, 2026
@codeant-ai

codeant-ai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@codeant-ai codeant-ai Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant