intentseal 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,202 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
202
+
@@ -0,0 +1,4 @@
1
+ IntentSeal
2
+ Copyright 2026 Sankalp Wanjari
3
+
4
+ This product is licensed under the Apache License, Version 2.0 (see LICENSE).
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: intentseal
3
+ Version: 0.1.0
4
+ Summary: IntentSeal: a prompt-injection firewall for AI agents. Inspect what your agent reads, check what it does.
5
+ Author-email: Sankalp Wanjari <sankalpwanjari85@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/sankalp2515/intentseal
8
+ Project-URL: Source, https://github.com/sankalp2515/intentseal
9
+ Project-URL: Documentation, https://github.com/sankalp2515/intentseal/blob/main/docs/INTEGRATION.md
10
+ Project-URL: Issues, https://github.com/sankalp2515/intentseal/issues
11
+ Keywords: prompt injection,llm security,ai agents,guardrails,firewall,rag,langchain
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Security
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ License-File: NOTICE
24
+ Requires-Dist: intentseal-core==0.1.0
25
+ Requires-Dist: httpx>=0.27
26
+ Provides-Extra: embedded
27
+ Requires-Dist: intentseal-core[classifiers,engine,ocr]==0.1.0; extra == "embedded"
28
+ Provides-Extra: langchain
29
+ Requires-Dist: langchain-core>=0.3; extra == "langchain"
30
+ Dynamic: license-file
31
+
32
+ # IntentSeal
33
+
34
+ **A prompt-injection firewall for AI agents.** IntentSeal inspects everything your agent reads (user messages, web
35
+ pages, PDFs, Word files, emails, Markdown, HTML, API responses, OCR text, source code, images, retrieved RAG passages)
36
+ before the model sees it, and checks every tool call before it runs. It keeps the agent inside the user's intent.
37
+
38
+ - **Detects and neutralises** instruction override, role change, secret extraction, tool abuse, credential theft,
39
+ context poisoning, multi-step jailbreaks, encoded instructions and indirect prompt injection. Every decision is
40
+ labelled with the attack type.
41
+ - **Sees what the model sees**: hidden text in PDFs and Word files, CSS-hidden HTML, HTML-only email parts, look-alike
42
+ letters and encoded payloads (base64, hex, %-encoding, Unicode tags) are decoded and checked.
43
+ - **Rules decide, AI advises**: fast structural rules and a local classifier first, an AI judge only when needed, and
44
+ deterministic action rules (allow-lists, pinned fields, outbound secret scan, "did the user ask for this?") that a
45
+ model can never override.
46
+ - **Any model provider**: the SDK checks content and tools, not the model call, so it works with OpenAI, Anthropic,
47
+ Gemini, Groq, Mistral, local models or anything else.
48
+
49
+ > Status: alpha (0.1). APIs may change between minor versions.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install intentseal # talks to an IntentSeal gateway (remote mode): small install
55
+ pip install "intentseal[embedded]" # runs the engine in your process (PDF/Word/HTML extraction, classifier, OCR)
56
+ pip install "intentseal[langchain]" # LangChain / LangGraph tool helper
57
+ ```
58
+
59
+ Python 3.11+.
60
+
61
+ ## Quickstart (embedded)
62
+
63
+ ```bash
64
+ intentseal init --agent my-agent --template research-assistant # writes ~/.intentseal/policies/my-agent.yaml
65
+ intentseal init --list # other templates (RAG, support, coding agent)
66
+ ```
67
+
68
+ Add one provider key for the AI judge to `~/.intentseal/.env` or your project's `.env` (`GROQ_API_KEY`,
69
+ `GEMINI_API_KEY`, `NVIDIA_API_KEY` or `OPENROUTER_API_KEY`). Then:
70
+
71
+ ```python
72
+ from intentseal import Guard
73
+
74
+ guard = Guard("my-agent")
75
+
76
+ with guard.session(user_id="u-42", task=user_message):
77
+ page = guard.inspect(html, source="web") # cleaned content, or a short safe notice
78
+ doc = guard.inspect(pdf_bytes, source="pdf", filename="q3.pdf") # raw bytes: hidden text is seen too
79
+
80
+ @guard.tool() # arguments checked before it runs
81
+ def send_message(to: str, subject: str, body: str): ...
82
+
83
+ send_message(to="someone@elsewhere.example", subject="...", body="...") # blocked: returns a notice
84
+ ```
85
+
86
+ The policy (`~/.intentseal/policies/my-agent.yaml`) lists your agent's tools with their risk, allow-lists (recipients,
87
+ domains, paths), pinned fields and the words that count as the user asking for an action. Any tool not listed is
88
+ blocked in enforce mode; `mode: monitor` records everything and blocks nothing.
89
+
90
+ ## Remote mode (a shared gateway, Console, audit trail)
91
+
92
+ ```python
93
+ guard = Guard("my-agent", remote="https://intentseal.example.com", api_key=os.environ["INTENTSEAL_KEY"])
94
+ ```
95
+
96
+ Same API; decisions come from the gateway, which also offers an OpenAI- and Anthropic-compatible **LLM proxy** (protect
97
+ an agent by changing its base URL only), a Security Console (decisions, approvals, session replay, policy) and SIEM
98
+ export.
99
+
100
+ ## RAG
101
+
102
+ Check files when you index them and passages when you retrieve them:
103
+
104
+ ```python
105
+ r = guard.inspect_result(open(path, "rb").read(), source="rag", filename=path)
106
+ if r.enforced.value in ("BLOCK", "ESCALATE"):
107
+ quarantine(path) # never enters the index
108
+ else:
109
+ index(r.cleaned_content)
110
+
111
+ with guard.session(user_id=user.id, task=question):
112
+ passages = [guard.inspect(c.text, source="rag") for c in retriever.search(question)]
113
+ ```
114
+
115
+ ## LangChain / LangGraph
116
+
117
+ ```python
118
+ tools = guard.protect_tools([search_tool, email_tool]) # inputs checked, outputs inspected
119
+ ```
120
+
121
+ ## Links
122
+
123
+ Source, documentation, evaluation results and the gateway: https://github.com/sankalp2515/intentseal
124
+
125
+ Licensed under the Apache License 2.0.
@@ -0,0 +1,94 @@
1
+ # IntentSeal
2
+
3
+ **A prompt-injection firewall for AI agents.** IntentSeal inspects everything your agent reads (user messages, web
4
+ pages, PDFs, Word files, emails, Markdown, HTML, API responses, OCR text, source code, images, retrieved RAG passages)
5
+ before the model sees it, and checks every tool call before it runs. It keeps the agent inside the user's intent.
6
+
7
+ - **Detects and neutralises** instruction override, role change, secret extraction, tool abuse, credential theft,
8
+ context poisoning, multi-step jailbreaks, encoded instructions and indirect prompt injection. Every decision is
9
+ labelled with the attack type.
10
+ - **Sees what the model sees**: hidden text in PDFs and Word files, CSS-hidden HTML, HTML-only email parts, look-alike
11
+ letters and encoded payloads (base64, hex, %-encoding, Unicode tags) are decoded and checked.
12
+ - **Rules decide, AI advises**: fast structural rules and a local classifier first, an AI judge only when needed, and
13
+ deterministic action rules (allow-lists, pinned fields, outbound secret scan, "did the user ask for this?") that a
14
+ model can never override.
15
+ - **Any model provider**: the SDK checks content and tools, not the model call, so it works with OpenAI, Anthropic,
16
+ Gemini, Groq, Mistral, local models or anything else.
17
+
18
+ > Status: alpha (0.1). APIs may change between minor versions.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install intentseal # talks to an IntentSeal gateway (remote mode): small install
24
+ pip install "intentseal[embedded]" # runs the engine in your process (PDF/Word/HTML extraction, classifier, OCR)
25
+ pip install "intentseal[langchain]" # LangChain / LangGraph tool helper
26
+ ```
27
+
28
+ Python 3.11+.
29
+
30
+ ## Quickstart (embedded)
31
+
32
+ ```bash
33
+ intentseal init --agent my-agent --template research-assistant # writes ~/.intentseal/policies/my-agent.yaml
34
+ intentseal init --list # other templates (RAG, support, coding agent)
35
+ ```
36
+
37
+ Add one provider key for the AI judge to `~/.intentseal/.env` or your project's `.env` (`GROQ_API_KEY`,
38
+ `GEMINI_API_KEY`, `NVIDIA_API_KEY` or `OPENROUTER_API_KEY`). Then:
39
+
40
+ ```python
41
+ from intentseal import Guard
42
+
43
+ guard = Guard("my-agent")
44
+
45
+ with guard.session(user_id="u-42", task=user_message):
46
+ page = guard.inspect(html, source="web") # cleaned content, or a short safe notice
47
+ doc = guard.inspect(pdf_bytes, source="pdf", filename="q3.pdf") # raw bytes: hidden text is seen too
48
+
49
+ @guard.tool() # arguments checked before it runs
50
+ def send_message(to: str, subject: str, body: str): ...
51
+
52
+ send_message(to="someone@elsewhere.example", subject="...", body="...") # blocked: returns a notice
53
+ ```
54
+
55
+ The policy (`~/.intentseal/policies/my-agent.yaml`) lists your agent's tools with their risk, allow-lists (recipients,
56
+ domains, paths), pinned fields and the words that count as the user asking for an action. Any tool not listed is
57
+ blocked in enforce mode; `mode: monitor` records everything and blocks nothing.
58
+
59
+ ## Remote mode (a shared gateway, Console, audit trail)
60
+
61
+ ```python
62
+ guard = Guard("my-agent", remote="https://intentseal.example.com", api_key=os.environ["INTENTSEAL_KEY"])
63
+ ```
64
+
65
+ Same API; decisions come from the gateway, which also offers an OpenAI- and Anthropic-compatible **LLM proxy** (protect
66
+ an agent by changing its base URL only), a Security Console (decisions, approvals, session replay, policy) and SIEM
67
+ export.
68
+
69
+ ## RAG
70
+
71
+ Check files when you index them and passages when you retrieve them:
72
+
73
+ ```python
74
+ r = guard.inspect_result(open(path, "rb").read(), source="rag", filename=path)
75
+ if r.enforced.value in ("BLOCK", "ESCALATE"):
76
+ quarantine(path) # never enters the index
77
+ else:
78
+ index(r.cleaned_content)
79
+
80
+ with guard.session(user_id=user.id, task=question):
81
+ passages = [guard.inspect(c.text, source="rag") for c in retriever.search(question)]
82
+ ```
83
+
84
+ ## LangChain / LangGraph
85
+
86
+ ```python
87
+ tools = guard.protect_tools([search_tool, email_tool]) # inputs checked, outputs inspected
88
+ ```
89
+
90
+ ## Links
91
+
92
+ Source, documentation, evaluation results and the gateway: https://github.com/sankalp2515/intentseal
93
+
94
+ Licensed under the Apache License 2.0.
@@ -0,0 +1,7 @@
1
+ """IntentSeal Python SDK."""
2
+
3
+ from intentseal_core.types import ActionResult, Decision, InspectResult
4
+
5
+ from .guard import Guard, ToolBlocked
6
+
7
+ __all__ = ["Guard", "ToolBlocked", "Decision", "InspectResult", "ActionResult"]
@@ -0,0 +1,255 @@
1
+ """Python SDK (SDK-1…SDK-5): protect an existing agent with a few lines.
2
+
3
+ guard = Guard("research-agent") # SDK-1 embedded mode (engine in-process)
4
+ guard = Guard("research-agent", remote="https://gw", api_key=...) # SDK-6 remote mode (Gateway API)
5
+ tools = guard.protect_tools(langchain_tools) # SDK-7 LangChain / LangGraph tools
6
+ with guard.session(user_id="u1", task="Summarise my inbox", trusted={"customer_id": "C-1042"}): # SDK-4
7
+ text = guard.inspect(raw_bytes, source="pdf", filename="report.pdf") # SDK-2
8
+ send_email(to=..., body=...) # decorated with @guard.tool() # SDK-3
9
+
10
+ Failure policy (SDK-5): if the engine itself fails, tools marked `failure: closed` in the policy (write/send)
11
+ are blocked, and `failure: open` tools (read-only) proceed; both are logged.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import asyncio
17
+ import contextvars
18
+ import functools
19
+ import inspect
20
+ import logging
21
+ from contextlib import contextmanager
22
+ from typing import TYPE_CHECKING, Any, Callable, Iterator
23
+
24
+ from intentseal_core.policy import AgentPolicy, load_policy, policy_exists, policy_path
25
+ from intentseal_core.session import Session
26
+ from intentseal_core.types import ActionResult, Decision, InspectResult
27
+
28
+ if TYPE_CHECKING:
29
+ from intentseal_core.engine import Engine
30
+
31
+ log = logging.getLogger("intentseal.sdk")
32
+ _current: contextvars.ContextVar[Session | None] = contextvars.ContextVar("intentseal_session", default=None)
33
+
34
+
35
+ class ToolBlocked(Exception):
36
+ """Raised by a guarded tool when `on_block="raise"`."""
37
+
38
+ def __init__(self, result: ActionResult) -> None:
39
+ super().__init__(result.notice or result.reason)
40
+ self.result = result
41
+
42
+
43
+ class Guard:
44
+ def __init__(self, agent_id: str, engine: Engine | None = None, mode: str | None = None,
45
+ policy: AgentPolicy | None = None, secrets: list[str] | None = None,
46
+ engine_options: dict[str, Any] | None = None, warm_up: bool = True,
47
+ remote: str | None = None, api_key: str | None = None, http_client: Any = None) -> None:
48
+ self.agent_id = agent_id
49
+ if remote or http_client is not None: # SDK-6: decisions come from the Gateway API
50
+ from .remote import RemoteEngine
51
+ engine = RemoteEngine(remote or "http://gateway", api_key or "", client=http_client)
52
+ policy = policy or engine.policy(agent_id) # failure policy (SDK-5) uses the gateway's tool settings
53
+ warm_up = False
54
+ if engine is None:
55
+ # Embedded mode runs the engine in-process; remote mode (above) needs only the light install.
56
+ try:
57
+ from intentseal_core.engine import Engine
58
+ from intentseal_core.llm.router import Router
59
+ except ImportError as e:
60
+ raise ImportError(
61
+ f"Embedded mode needs the engine: pip install 'intentseal[embedded]' (missing: {e.name}). "
62
+ "Or use a gateway: Guard(agent_id, remote='https://<gateway>', api_key=...).") from e
63
+ if policy is None and remote is None and http_client is None and not policy_exists(agent_id):
64
+ log.warning("No policy file for agent '%s' (%s): the default policy runs in monitor mode, so nothing is "
65
+ "enforced. Create one with `intentseal init --agent %s`, or pass mode='enforce'.",
66
+ agent_id, policy_path(agent_id), agent_id)
67
+ self._policy = policy or load_policy(agent_id)
68
+ if mode:
69
+ self._policy.mode = mode
70
+ if engine is None:
71
+ router = Router() # shares the persistent store (settings.db_path) for usage, cache and decisions
72
+ opts = {"tier0_timeout_s": 10.0, **(engine_options or {})} # extraction incl. OCR on CPU
73
+ engine = Engine(policy_loader=self._load, router=router, store=router.store, **opts)
74
+ self.engine = engine
75
+ if secrets:
76
+ self.engine.register_secrets(secrets)
77
+ if warm_up:
78
+ _warm_up()
79
+
80
+ def _load(self, agent_id: str) -> AgentPolicy:
81
+ return self._policy if agent_id == self.agent_id else load_policy(agent_id)
82
+
83
+ @property
84
+ def policy(self) -> AgentPolicy:
85
+ return self._policy
86
+
87
+ # ---- sessions (SDK-4) --------------------------------------------------------------------
88
+ @contextmanager
89
+ def session(self, user_id: str | None = None, task: str = "", trusted: dict[str, Any] | None = None) -> Iterator[Session]:
90
+ s = self.engine.start_session(self.agent_id, user_ref=user_id, task=task, trusted=trusted)
91
+ token = _current.set(s)
92
+ try:
93
+ yield s
94
+ finally:
95
+ _current.reset(token)
96
+
97
+ def start_session(self, user_id: str | None = None, task: str = "", trusted: dict[str, Any] | None = None) -> Session:
98
+ s = self.engine.start_session(self.agent_id, user_ref=user_id, task=task, trusted=trusted)
99
+ _current.set(s)
100
+ return s
101
+
102
+ @property
103
+ def current_session(self) -> Session | None:
104
+ return _current.get()
105
+
106
+ def _sid(self, session_id: str | None) -> str | None:
107
+ if session_id:
108
+ return session_id
109
+ s = _current.get()
110
+ return s.session_id if s else None
111
+
112
+ def register_secrets(self, values: list[str]) -> None:
113
+ self.engine.register_secrets(values)
114
+
115
+ # ---- inbound content (SDK-2) -------------------------------------------------------------
116
+ async def ainspect_result(self, content: bytes | str, source: str, filename: str | None = None,
117
+ session_id: str | None = None) -> InspectResult:
118
+ return await self.engine.inspect(content, source, self.agent_id, session_id=self._sid(session_id),
119
+ filename=filename)
120
+
121
+ async def ainspect(self, content: bytes | str, source: str, filename: str | None = None,
122
+ session_id: str | None = None) -> str:
123
+ """Return what the agent should see: cleaned content, or a safe notice when blocked."""
124
+ try:
125
+ r = await self.ainspect_result(content, source, filename, session_id)
126
+ except Exception as e: # inspection unavailable (e.g. remote gateway down): never pass content unseen
127
+ log.warning("inspection failed for %s (%s); content withheld", source, type(e).__name__)
128
+ return f"[Content from {source} was withheld: the security check is unavailable]"
129
+ if r.enforced == Decision.ALLOW and isinstance(content, str):
130
+ return content # allowed text passes unchanged (G3b); files return their extracted view
131
+ if r.enforced == Decision.BLOCK:
132
+ return f"[Content from {source} was withheld by the security policy: {r.reason}]"
133
+ if r.enforced == Decision.ESCALATE:
134
+ return f"[Content from {source} is held for human review: {r.reason}]"
135
+ return r.cleaned_content
136
+
137
+ def inspect(self, content: bytes | str, source: str, filename: str | None = None, session_id: str | None = None) -> str:
138
+ return _run(self.ainspect(content, source, filename, session_id))
139
+
140
+ def inspect_result(self, content: bytes | str, source: str, filename: str | None = None,
141
+ session_id: str | None = None) -> InspectResult:
142
+ return _run(self.ainspect_result(content, source, filename, session_id))
143
+
144
+ # ---- the model's answer -------------------------------------------------------------------
145
+ async def acheck_output(self, text: str, session_id: str | None = None) -> str:
146
+ """What the user may see of the model's final answer: protected values (canaries, registered secrets, known
147
+ secret formats, also encoded) are redacted, or the answer is withheld when they cannot be."""
148
+ try:
149
+ r = await self.engine.check_output(text, self.agent_id, session_id=self._sid(session_id))
150
+ except Exception as e: # the check is unavailable: the answer is not known to be safe
151
+ log.warning("answer check failed (%s); answer withheld", type(e).__name__)
152
+ return "[The answer was withheld: the security check is unavailable]"
153
+ return r.cleaned_content
154
+
155
+ def check_output(self, text: str, session_id: str | None = None) -> str:
156
+ return _run(self.acheck_output(text, session_id))
157
+
158
+ # ---- actions (SDK-3, SDK-5) --------------------------------------------------------------
159
+ async def acheck(self, tool: str, args: dict[str, Any], session_id: str | None = None) -> ActionResult:
160
+ try:
161
+ return await self.engine.check_action(tool, args, self.agent_id, session_id=self._sid(session_id))
162
+ except Exception as e: # engine failure: apply the tool's failure policy
163
+ tp = self._policy.tools.get(tool)
164
+ closed = (tp.failure if tp else "closed") == "closed"
165
+ log.warning("guard check failed for %s (%s); failing %s", tool, type(e).__name__, "closed" if closed else "open")
166
+ d = Decision.BLOCK if closed else Decision.ALLOW
167
+ return ActionResult(decision=d, enforced=d, final_args=None if closed else dict(args), degraded=True,
168
+ policy_rule=f"guard-failure-fail-{'closed' if closed else 'open'}",
169
+ reason=f"guard unavailable ({type(e).__name__})",
170
+ notice=f"Action '{tool}' was not performed: the security check is unavailable." if closed else None)
171
+
172
+ def check(self, tool: str, args: dict[str, Any], session_id: str | None = None) -> ActionResult:
173
+ return _run(self.acheck(tool, args, session_id))
174
+
175
+ def tool(self, name: str | None = None, pinned: dict[str, Any] | None = None,
176
+ on_block: str = "notice") -> Callable[[Callable[..., Any]], Callable[..., Any]]:
177
+ """Decorate a tool function. Arguments are checked (and pinned fields rewritten) before it runs.
178
+
179
+ pinned: extra trusted values for this tool's arguments, merged into the session's trusted context.
180
+ on_block: "notice" returns the guard's notice string; "raise" raises ToolBlocked.
181
+ """
182
+
183
+ def deco(fn: Callable[..., Any]) -> Callable[..., Any]:
184
+ tool_name = name or fn.__name__
185
+ sig = inspect.signature(fn)
186
+
187
+ def bind(args: tuple, kwargs: dict) -> dict[str, Any]:
188
+ b = sig.bind_partial(*args, **kwargs)
189
+ return dict(b.arguments)
190
+
191
+ def apply_pins() -> None:
192
+ s = _current.get()
193
+ if s is not None and pinned:
194
+ s.trusted.update(pinned)
195
+ s.note_trusted(" ".join(str(v) for v in pinned.values()))
196
+
197
+ def blocked(r: ActionResult) -> Any:
198
+ if on_block == "raise":
199
+ raise ToolBlocked(r)
200
+ return r.notice or f"Action '{tool_name}' was blocked by the security policy."
201
+
202
+ if inspect.iscoroutinefunction(fn):
203
+ @functools.wraps(fn)
204
+ async def awrapper(*args: Any, **kwargs: Any) -> Any:
205
+ apply_pins()
206
+ r = await self.acheck(tool_name, bind(args, kwargs))
207
+ if r.enforced != Decision.ALLOW:
208
+ return blocked(r)
209
+ return await fn(**(r.final_args or {}))
210
+ return awrapper
211
+
212
+ @functools.wraps(fn)
213
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
214
+ apply_pins()
215
+ r = self.check(tool_name, bind(args, kwargs))
216
+ if r.enforced != Decision.ALLOW:
217
+ return blocked(r)
218
+ return fn(**(r.final_args or {}))
219
+ return wrapper
220
+
221
+ return deco
222
+
223
+
224
+ def protect_tools(self, tools: Any, inspect_outputs: bool = True) -> list[Any]:
225
+ """SDK-7: wrap LangChain / LangGraph tools (see intentseal.langchain)."""
226
+ from .langchain import protect_tools
227
+ return protect_tools(self, tools, inspect_outputs)
228
+
229
+
230
+ def _warm_up() -> None:
231
+ """Load the local classifier and OCR once so the first real inspection is not slowed down."""
232
+ try:
233
+ import io
234
+
235
+ from PIL import Image, ImageDraw
236
+
237
+ from intentseal_core import extract as _extract
238
+ from intentseal_core.classifier import local_classifier
239
+
240
+ local_classifier()
241
+ img = Image.new("RGB", (160, 40), "white")
242
+ ImageDraw.Draw(img).text((5, 10), "warm up", fill="black")
243
+ buf = io.BytesIO()
244
+ img.save(buf, format="PNG")
245
+ _extract.extract(buf.getvalue(), fmt="image")
246
+ except Exception as e: # warm-up is best effort
247
+ log.info("warm-up skipped (%s)", type(e).__name__)
248
+
249
+
250
+ def _run(coro: Any) -> Any:
251
+ try:
252
+ asyncio.get_running_loop()
253
+ except RuntimeError:
254
+ return asyncio.run(coro)
255
+ raise RuntimeError("Guard sync methods cannot run inside an event loop; use the async variants (ainspect/acheck).")
@@ -0,0 +1,52 @@
1
+ """LangChain / LangGraph helper (SDK-7): protect an existing tool list in one line.
2
+
3
+ from intentseal import Guard
4
+ guard = Guard("research-agent")
5
+ tools = guard.protect_tools(tools) # or: protect_tools(guard, tools)
6
+ with guard.session(user_id="u1", task=task):
7
+ agent.invoke(...)
8
+
9
+ Every call is checked by the action guard before the tool runs (pinned fields rewritten, blocked calls return the
10
+ guard's notice to the model instead of running), and every tool result is inspected as untrusted content before
11
+ the model sees it. The wrapped tools keep their name, description and argument schema, so prompts and
12
+ tool-calling behaviour do not change.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import TYPE_CHECKING, Any, Iterable
18
+
19
+ from intentseal_core.types import Decision
20
+
21
+ if TYPE_CHECKING:
22
+ from langchain_core.tools import BaseTool
23
+
24
+ from .guard import Guard
25
+
26
+
27
+ def protect_tools(guard: "Guard", tools: Iterable["BaseTool"], inspect_outputs: bool = True) -> list["BaseTool"]:
28
+ from langchain_core.tools import StructuredTool
29
+
30
+ def wrap(t: "BaseTool") -> "BaseTool":
31
+ async def acall(**kwargs: Any) -> Any:
32
+ r = await guard.acheck(t.name, kwargs)
33
+ if r.enforced != Decision.ALLOW:
34
+ return r.notice or f"Action '{t.name}' was blocked by the security policy."
35
+ out = await t.ainvoke(r.final_args if r.final_args is not None else kwargs)
36
+ if inspect_outputs and isinstance(out, str):
37
+ out = await guard.ainspect(out, source=f"tool:{t.name}")
38
+ return out
39
+
40
+ def call(**kwargs: Any) -> Any:
41
+ r = guard.check(t.name, kwargs)
42
+ if r.enforced != Decision.ALLOW:
43
+ return r.notice or f"Action '{t.name}' was blocked by the security policy."
44
+ out = t.invoke(r.final_args if r.final_args is not None else kwargs)
45
+ if inspect_outputs and isinstance(out, str):
46
+ out = guard.inspect(out, source=f"tool:{t.name}")
47
+ return out
48
+
49
+ return StructuredTool(name=t.name, description=t.description, args_schema=t.args_schema,
50
+ func=call, coroutine=acall, return_direct=getattr(t, "return_direct", False))
51
+
52
+ return [wrap(t) for t in tools]
@@ -0,0 +1,73 @@
1
+ """Remote mode (SDK-6): the Guard talks to a Gateway API instead of running the engine in-process.
2
+
3
+ guard = Guard("research-agent", remote="https://gateway.example", api_key=os.environ["INTENTSEAL_API_KEY"])
4
+
5
+ `RemoteEngine` implements the part of the Engine interface the Guard uses (start_session, inspect, check_action,
6
+ register_secrets), so every SDK feature works the same way; decisions are made by the gateway's engine.
7
+ Differences from embedded mode:
8
+ - the session's trusted context (pinned fields) is fixed when the session is created;
9
+ - canaries/secrets are configured on the gateway (INTENTSEAL_CANARIES), not registered from the client;
10
+ - if the gateway is unreachable, content is withheld (fail closed) and tools follow their failure policy (SDK-5).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import base64
17
+ import logging
18
+ from typing import Any, Iterable
19
+
20
+ import httpx
21
+
22
+ from intentseal_core.policy import AgentPolicy
23
+ from intentseal_core.session import Session
24
+ from intentseal_core.types import ActionResult, InspectResult
25
+
26
+ log = logging.getLogger("intentseal.sdk.remote")
27
+
28
+
29
+ class RemoteEngine:
30
+ def __init__(self, base_url: str, api_key: str, timeout_s: float = 30.0, client: httpx.Client | None = None) -> None:
31
+ self.http = client or httpx.Client(base_url=base_url.rstrip("/"), timeout=timeout_s)
32
+ self.headers = {"Authorization": f"Bearer {api_key}"}
33
+
34
+ def _post(self, path: str, body: dict[str, Any]) -> dict[str, Any]:
35
+ r = self.http.post(path, json=body, headers=self.headers)
36
+ r.raise_for_status()
37
+ return r.json()
38
+
39
+ def policy(self, agent_id: str) -> AgentPolicy:
40
+ r = self.http.get(f"/v1/policies/{agent_id}", headers=self.headers)
41
+ r.raise_for_status()
42
+ return AgentPolicy.model_validate(r.json())
43
+
44
+ def register_secrets(self, values: Iterable[str]) -> None:
45
+ if list(values):
46
+ log.info("remote mode: secrets/canaries are configured on the gateway (INTENTSEAL_CANARIES), not sent from the client")
47
+
48
+ def start_session(self, agent_id: str, user_ref: str | None = None, task: str = "",
49
+ trusted: dict[str, Any] | None = None) -> Session:
50
+ sid = self._post("/v1/sessions", {"agent_id": agent_id, "user_ref": user_ref, "task": task,
51
+ "trusted": dict(trusted or {})})["session_id"]
52
+ return Session(agent_id=agent_id, session_id=sid, user_ref=user_ref, task=task, trusted=dict(trusted or {}))
53
+
54
+ async def inspect(self, content: str | bytes, source: str, agent_id: str, session_id: str | None = None,
55
+ filename: str | None = None, fmt: str | None = None, task: str | None = None) -> InspectResult:
56
+ body: dict[str, Any] = {"source": source, "agent_id": agent_id, "session_id": session_id, "filename": filename}
57
+ if isinstance(content, bytes):
58
+ body["content_b64"] = base64.b64encode(content).decode()
59
+ else:
60
+ body["content"] = content
61
+ return InspectResult.model_validate(await asyncio.to_thread(self._post, "/v1/inspect", body))
62
+
63
+ async def check_action(self, tool: str, args: dict[str, Any], agent_id: str,
64
+ session_id: str | None = None) -> ActionResult:
65
+ body = {"tool": tool, "args": args, "agent_id": agent_id, "session_id": session_id}
66
+ return ActionResult.model_validate(await asyncio.to_thread(self._post, "/v1/actions/check", body))
67
+
68
+ async def check_output(self, text: str, agent_id: str, session_id: str | None = None) -> InspectResult:
69
+ body = {"agent_id": agent_id, "session_id": session_id, "text": text}
70
+ return InspectResult.model_validate(await asyncio.to_thread(self._post, "/v1/outputs/check", body))
71
+
72
+ def close(self) -> None:
73
+ self.http.close()
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: intentseal
3
+ Version: 0.1.0
4
+ Summary: IntentSeal: a prompt-injection firewall for AI agents. Inspect what your agent reads, check what it does.
5
+ Author-email: Sankalp Wanjari <sankalpwanjari85@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/sankalp2515/intentseal
8
+ Project-URL: Source, https://github.com/sankalp2515/intentseal
9
+ Project-URL: Documentation, https://github.com/sankalp2515/intentseal/blob/main/docs/INTEGRATION.md
10
+ Project-URL: Issues, https://github.com/sankalp2515/intentseal/issues
11
+ Keywords: prompt injection,llm security,ai agents,guardrails,firewall,rag,langchain
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Security
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ License-File: NOTICE
24
+ Requires-Dist: intentseal-core==0.1.0
25
+ Requires-Dist: httpx>=0.27
26
+ Provides-Extra: embedded
27
+ Requires-Dist: intentseal-core[classifiers,engine,ocr]==0.1.0; extra == "embedded"
28
+ Provides-Extra: langchain
29
+ Requires-Dist: langchain-core>=0.3; extra == "langchain"
30
+ Dynamic: license-file
31
+
32
+ # IntentSeal
33
+
34
+ **A prompt-injection firewall for AI agents.** IntentSeal inspects everything your agent reads (user messages, web
35
+ pages, PDFs, Word files, emails, Markdown, HTML, API responses, OCR text, source code, images, retrieved RAG passages)
36
+ before the model sees it, and checks every tool call before it runs. It keeps the agent inside the user's intent.
37
+
38
+ - **Detects and neutralises** instruction override, role change, secret extraction, tool abuse, credential theft,
39
+ context poisoning, multi-step jailbreaks, encoded instructions and indirect prompt injection. Every decision is
40
+ labelled with the attack type.
41
+ - **Sees what the model sees**: hidden text in PDFs and Word files, CSS-hidden HTML, HTML-only email parts, look-alike
42
+ letters and encoded payloads (base64, hex, %-encoding, Unicode tags) are decoded and checked.
43
+ - **Rules decide, AI advises**: fast structural rules and a local classifier first, an AI judge only when needed, and
44
+ deterministic action rules (allow-lists, pinned fields, outbound secret scan, "did the user ask for this?") that a
45
+ model can never override.
46
+ - **Any model provider**: the SDK checks content and tools, not the model call, so it works with OpenAI, Anthropic,
47
+ Gemini, Groq, Mistral, local models or anything else.
48
+
49
+ > Status: alpha (0.1). APIs may change between minor versions.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install intentseal # talks to an IntentSeal gateway (remote mode): small install
55
+ pip install "intentseal[embedded]" # runs the engine in your process (PDF/Word/HTML extraction, classifier, OCR)
56
+ pip install "intentseal[langchain]" # LangChain / LangGraph tool helper
57
+ ```
58
+
59
+ Python 3.11+.
60
+
61
+ ## Quickstart (embedded)
62
+
63
+ ```bash
64
+ intentseal init --agent my-agent --template research-assistant # writes ~/.intentseal/policies/my-agent.yaml
65
+ intentseal init --list # other templates (RAG, support, coding agent)
66
+ ```
67
+
68
+ Add one provider key for the AI judge to `~/.intentseal/.env` or your project's `.env` (`GROQ_API_KEY`,
69
+ `GEMINI_API_KEY`, `NVIDIA_API_KEY` or `OPENROUTER_API_KEY`). Then:
70
+
71
+ ```python
72
+ from intentseal import Guard
73
+
74
+ guard = Guard("my-agent")
75
+
76
+ with guard.session(user_id="u-42", task=user_message):
77
+ page = guard.inspect(html, source="web") # cleaned content, or a short safe notice
78
+ doc = guard.inspect(pdf_bytes, source="pdf", filename="q3.pdf") # raw bytes: hidden text is seen too
79
+
80
+ @guard.tool() # arguments checked before it runs
81
+ def send_message(to: str, subject: str, body: str): ...
82
+
83
+ send_message(to="someone@elsewhere.example", subject="...", body="...") # blocked: returns a notice
84
+ ```
85
+
86
+ The policy (`~/.intentseal/policies/my-agent.yaml`) lists your agent's tools with their risk, allow-lists (recipients,
87
+ domains, paths), pinned fields and the words that count as the user asking for an action. Any tool not listed is
88
+ blocked in enforce mode; `mode: monitor` records everything and blocks nothing.
89
+
90
+ ## Remote mode (a shared gateway, Console, audit trail)
91
+
92
+ ```python
93
+ guard = Guard("my-agent", remote="https://intentseal.example.com", api_key=os.environ["INTENTSEAL_KEY"])
94
+ ```
95
+
96
+ Same API; decisions come from the gateway, which also offers an OpenAI- and Anthropic-compatible **LLM proxy** (protect
97
+ an agent by changing its base URL only), a Security Console (decisions, approvals, session replay, policy) and SIEM
98
+ export.
99
+
100
+ ## RAG
101
+
102
+ Check files when you index them and passages when you retrieve them:
103
+
104
+ ```python
105
+ r = guard.inspect_result(open(path, "rb").read(), source="rag", filename=path)
106
+ if r.enforced.value in ("BLOCK", "ESCALATE"):
107
+ quarantine(path) # never enters the index
108
+ else:
109
+ index(r.cleaned_content)
110
+
111
+ with guard.session(user_id=user.id, task=question):
112
+ passages = [guard.inspect(c.text, source="rag") for c in retriever.search(question)]
113
+ ```
114
+
115
+ ## LangChain / LangGraph
116
+
117
+ ```python
118
+ tools = guard.protect_tools([search_tool, email_tool]) # inputs checked, outputs inspected
119
+ ```
120
+
121
+ ## Links
122
+
123
+ Source, documentation, evaluation results and the gateway: https://github.com/sankalp2515/intentseal
124
+
125
+ Licensed under the Apache License 2.0.
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ NOTICE
3
+ README.md
4
+ pyproject.toml
5
+ intentseal/__init__.py
6
+ intentseal/guard.py
7
+ intentseal/langchain.py
8
+ intentseal/remote.py
9
+ intentseal.egg-info/PKG-INFO
10
+ intentseal.egg-info/SOURCES.txt
11
+ intentseal.egg-info/dependency_links.txt
12
+ intentseal.egg-info/requires.txt
13
+ intentseal.egg-info/top_level.txt
@@ -0,0 +1,8 @@
1
+ intentseal-core==0.1.0
2
+ httpx>=0.27
3
+
4
+ [embedded]
5
+ intentseal-core[classifiers,engine,ocr]==0.1.0
6
+
7
+ [langchain]
8
+ langchain-core>=0.3
@@ -0,0 +1 @@
1
+ intentseal
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "intentseal"
7
+ version = "0.1.0"
8
+ description = "IntentSeal: a prompt-injection firewall for AI agents. Inspect what your agent reads, check what it does."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "Apache-2.0"
12
+ license-files = ["LICENSE", "NOTICE"]
13
+ authors = [{ name = "Sankalp Wanjari", email = "sankalpwanjari85@gmail.com" }]
14
+ keywords = ["prompt injection", "llm security", "ai agents", "guardrails", "firewall", "rag", "langchain"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Security",
23
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
24
+ ]
25
+ # Remote mode (a shared IntentSeal gateway) needs only this. The publish workflow keeps the two versions equal.
26
+ dependencies = ["intentseal-core==0.1.0", "httpx>=0.27"]
27
+
28
+ [project.optional-dependencies]
29
+ embedded = ["intentseal-core[engine,classifiers,ocr]==0.1.0"] # run the engine in-process
30
+ langchain = ["langchain-core>=0.3"]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/sankalp2515/intentseal"
34
+ Source = "https://github.com/sankalp2515/intentseal"
35
+ Documentation = "https://github.com/sankalp2515/intentseal/blob/main/docs/INTEGRATION.md"
36
+ Issues = "https://github.com/sankalp2515/intentseal/issues"
37
+
38
+ [tool.setuptools.packages.find]
39
+ where = ["."]
40
+ include = ["intentseal*"]
41
+ exclude = ["intentseal_core*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+