langgraph-secure-agents 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.
Files changed (31) hide show
  1. langgraph_secure_agents-0.1.0/.github/dependabot.yml +13 -0
  2. langgraph_secure_agents-0.1.0/.github/workflows/ci.yml +186 -0
  3. langgraph_secure_agents-0.1.0/.github/workflows/release.yml +168 -0
  4. langgraph_secure_agents-0.1.0/.gitignore +17 -0
  5. langgraph_secure_agents-0.1.0/CHANGELOG.md +23 -0
  6. langgraph_secure_agents-0.1.0/LICENSE +21 -0
  7. langgraph_secure_agents-0.1.0/PKG-INFO +238 -0
  8. langgraph_secure_agents-0.1.0/README.md +202 -0
  9. langgraph_secure_agents-0.1.0/SECURITY.md +24 -0
  10. langgraph_secure_agents-0.1.0/docs/releasing.md +59 -0
  11. langgraph_secure_agents-0.1.0/docs/threat-model.md +90 -0
  12. langgraph_secure_agents-0.1.0/examples/__init__.py +1 -0
  13. langgraph_secure_agents-0.1.0/examples/policy_guarded_workflow.py +148 -0
  14. langgraph_secure_agents-0.1.0/pyproject.toml +76 -0
  15. langgraph_secure_agents-0.1.0/src/langgraph_secure_agents/__init__.py +20 -0
  16. langgraph_secure_agents-0.1.0/src/langgraph_secure_agents/_resolver_safety.py +45 -0
  17. langgraph_secure_agents-0.1.0/src/langgraph_secure_agents/guard.py +130 -0
  18. langgraph_secure_agents-0.1.0/src/langgraph_secure_agents/middleware.py +344 -0
  19. langgraph_secure_agents-0.1.0/src/langgraph_secure_agents/models.py +205 -0
  20. langgraph_secure_agents-0.1.0/src/langgraph_secure_agents/policy.py +99 -0
  21. langgraph_secure_agents-0.1.0/src/langgraph_secure_agents/py.typed +0 -0
  22. langgraph_secure_agents-0.1.0/tests/test_approval_interrupt.py +106 -0
  23. langgraph_secure_agents-0.1.0/tests/test_async_middleware.py +225 -0
  24. langgraph_secure_agents-0.1.0/tests/test_example_workflow.py +32 -0
  25. langgraph_secure_agents-0.1.0/tests/test_guard.py +266 -0
  26. langgraph_secure_agents-0.1.0/tests/test_langgraph_approval_integration.py +448 -0
  27. langgraph_secure_agents-0.1.0/tests/test_middleware.py +820 -0
  28. langgraph_secure_agents-0.1.0/tests/test_models.py +254 -0
  29. langgraph_secure_agents-0.1.0/tests/test_package.py +5 -0
  30. langgraph_secure_agents-0.1.0/tests/test_policy.py +239 -0
  31. langgraph_secure_agents-0.1.0/tests/test_resolver_safety.py +28 -0
@@ -0,0 +1,13 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ open-pull-requests-limit: 5
8
+
9
+ - package-ecosystem: "github-actions"
10
+ directory: "/"
11
+ schedule:
12
+ interval: "weekly"
13
+ open-pull-requests-limit: 5
@@ -0,0 +1,186 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ quality:
14
+ name: Quality checks (Python ${{ matrix.python-version }})
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.11", "3.12", "3.13"]
20
+
21
+ steps:
22
+ - name: Check out repository
23
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+
25
+ - name: Set up Python
26
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
27
+ with:
28
+ python-version: ${{ matrix.python-version }}
29
+ cache: pip
30
+
31
+ - name: Install package and development dependencies
32
+ run: python -m pip install --upgrade pip && python -m pip install -e ".[dev]"
33
+
34
+ - name: Lint
35
+ run: ruff check .
36
+
37
+ - name: Check formatting
38
+ run: ruff format --check .
39
+
40
+ - name: Validate workflow YAML, action SHA pins, and release permissions
41
+ run: |
42
+ python - <<'PY'
43
+ import re
44
+ from pathlib import Path
45
+ import yaml
46
+
47
+ paths = (
48
+ Path(".github/workflows/ci.yml"),
49
+ Path(".github/workflows/release.yml"),
50
+ Path(".github/dependabot.yml"),
51
+ )
52
+ documents = {}
53
+ for path in paths:
54
+ with path.open(encoding="utf-8") as stream:
55
+ document = yaml.safe_load(stream)
56
+ if not isinstance(document, dict):
57
+ raise SystemExit(f"{path} must contain a YAML mapping")
58
+ documents[path.as_posix()] = document
59
+
60
+ sha_pattern = re.compile(r"^[0-9a-f]{40}$")
61
+ for path in paths[:2]:
62
+ for job_name, job in documents[path.as_posix()].get("jobs", {}).items():
63
+ for step in job.get("steps", []):
64
+ action = step.get("uses")
65
+ if action and ("@" not in action or not sha_pattern.fullmatch(action.rsplit("@", 1)[1].split()[0])):
66
+ raise SystemExit(f"{path}: {job_name} action is not pinned to a full SHA: {action}")
67
+
68
+ release = documents[".github/workflows/release.yml"]
69
+ jobs = release.get("jobs", {})
70
+ triggers = release.get("on", release.get(True, {}))
71
+ if triggers.get("push", {}).get("branches") != ["main"]:
72
+ raise SystemExit("automatic release workflow must run on pushes to main")
73
+ if jobs.get("release", {}).get("permissions", {}).get("contents") != "write":
74
+ raise SystemExit("versioning job must request contents: write")
75
+ if jobs.get("publish-pypi", {}).get("permissions", {}).get("id-token") != "write":
76
+ raise SystemExit("PyPI publishing job must request id-token: write")
77
+ if "contents" in jobs.get("publish-pypi", {}).get("permissions", {}):
78
+ raise SystemExit("PyPI publishing job must not request repository contents permissions")
79
+ if jobs.get("publish-pypi", {}).get("needs") != "release":
80
+ raise SystemExit("PyPI publishing must depend on the release job")
81
+ print("Workflow YAML, action SHA pins, and automatic-release permissions validated.")
82
+ PY
83
+
84
+ - name: Type check
85
+ run: mypy src
86
+
87
+ - name: Test
88
+ run: pytest --cov=langgraph_secure_agents --cov-report=term-missing --cov-fail-under=98
89
+
90
+ package-build:
91
+ name: Build and validate distributions
92
+ runs-on: ubuntu-latest
93
+ needs: quality
94
+ permissions:
95
+ contents: read
96
+
97
+ steps:
98
+ - name: Check out repository
99
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
100
+
101
+ - name: Set up Python
102
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
103
+ with:
104
+ python-version: "3.12"
105
+
106
+ - name: Install build tools
107
+ run: python -m pip install --upgrade pip build twine
108
+
109
+ - name: Build wheel and source distribution
110
+ run: python -m build
111
+
112
+ - name: Validate distribution metadata
113
+ run: python -m twine check dist/*
114
+
115
+ - name: Upload validated distributions
116
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
117
+ with:
118
+ name: package-dist
119
+ path: dist/
120
+ if-no-files-found: error
121
+ retention-days: 1
122
+
123
+ package-smoke:
124
+ name: Install and smoke-test distributions
125
+ runs-on: ubuntu-latest
126
+ needs: package-build
127
+ permissions:
128
+ contents: read
129
+
130
+ steps:
131
+ - name: Check out repository
132
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
133
+
134
+ - name: Set up Python
135
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
136
+ with:
137
+ python-version: "3.12"
138
+
139
+ - name: Download distributions built by the read-only job
140
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
141
+ with:
142
+ name: package-dist
143
+ path: dist/
144
+
145
+ - name: Install wheel into a clean environment
146
+ run: |
147
+ python -m venv /tmp/langgraph-secure-agents-smoke
148
+ /tmp/langgraph-secure-agents-smoke/bin/python -m pip install --no-deps dist/*.whl
149
+
150
+ - name: Smoke-test the installed wheel
151
+ working-directory: /tmp
152
+ run: |
153
+ /tmp/langgraph-secure-agents-smoke/bin/python - <<'PY'
154
+ from langgraph_secure_agents import AccessRequest, Effect, PolicyEngine, PolicyRule, Principal
155
+
156
+ policy = PolicyEngine([
157
+ PolicyRule(
158
+ rule_id="wheel-smoke-test",
159
+ effect=Effect.ALLOW,
160
+ actions=frozenset({"read"}),
161
+ resources=frozenset({"reports"}),
162
+ roles=frozenset({"analyst"}),
163
+ )
164
+ ])
165
+ decision = policy.evaluate(
166
+ AccessRequest(Principal("smoke-user", frozenset({"analyst"})), "read", "reports")
167
+ )
168
+ assert decision.allowed, decision
169
+ print("Installed wheel smoke test passed.")
170
+ PY
171
+
172
+ - name: Smoke-test optional LangChain agent extra
173
+ run: |
174
+ python -m venv /tmp/langgraph-secure-agents-extra
175
+ /tmp/langgraph-secure-agents-extra/bin/python -m pip install ".[langchain-agents]"
176
+ /tmp/langgraph-secure-agents-extra/bin/python - <<'PY'
177
+ from langgraph_secure_agents import PolicyEngine, Principal
178
+ from langgraph_secure_agents.middleware import create_authorization_middleware
179
+
180
+ middleware = create_authorization_middleware(
181
+ PolicyEngine(),
182
+ lambda context: Principal("smoke-user"),
183
+ )
184
+ assert callable(middleware.wrap_tool_call)
185
+ print("Optional middleware extra smoke test passed.")
186
+ PY
@@ -0,0 +1,168 @@
1
+ name: Automatic Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: automatic-release-${{ github.ref }}
13
+ cancel-in-progress: false
14
+
15
+ jobs:
16
+ quality:
17
+ name: Release quality gate (Python ${{ matrix.python-version }})
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python-version: ["3.11", "3.12", "3.13"]
23
+ permissions:
24
+ contents: read
25
+ steps:
26
+ - name: Check out repository
27
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
28
+ - name: Set up Python
29
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
30
+ with:
31
+ python-version: ${{ matrix.python-version }}
32
+ cache: pip
33
+ - name: Install development dependencies
34
+ run: python -m pip install --upgrade pip && python -m pip install -e ".[dev]"
35
+ - name: Lint
36
+ run: ruff check .
37
+ - name: Check formatting
38
+ run: ruff format --check .
39
+ - name: Type check
40
+ run: mypy src
41
+ - name: Test
42
+ run: pytest --cov=langgraph_secure_agents --cov-report=term-missing --cov-fail-under=98
43
+
44
+ package-smoke:
45
+ name: Build and smoke-test distributions
46
+ runs-on: ubuntu-latest
47
+ needs: quality
48
+ permissions:
49
+ contents: read
50
+ steps:
51
+ - name: Check out repository
52
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
53
+ - name: Set up Python
54
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
55
+ with:
56
+ python-version: "3.12"
57
+ - name: Build and validate distributions
58
+ run: |
59
+ python -m pip install --upgrade pip build twine
60
+ python -m build
61
+ python -m twine check dist/*
62
+ - name: Install wheel in a clean environment and smoke-test core API
63
+ run: |
64
+ python -m venv /tmp/langgraph-secure-agents-smoke
65
+ /tmp/langgraph-secure-agents-smoke/bin/python -m pip install --no-deps dist/*.whl
66
+ cd /tmp
67
+ /tmp/langgraph-secure-agents-smoke/bin/python - <<'PY'
68
+ from langgraph_secure_agents import AccessRequest, Effect, PolicyEngine, PolicyRule, Principal
69
+
70
+ policy = PolicyEngine([
71
+ PolicyRule(
72
+ rule_id="release-smoke-test",
73
+ effect=Effect.ALLOW,
74
+ actions=frozenset({"read"}),
75
+ resources=frozenset({"reports"}),
76
+ roles=frozenset({"analyst"}),
77
+ )
78
+ ])
79
+ result = policy.evaluate(
80
+ AccessRequest(Principal("release-user", frozenset({"analyst"})), "read", "reports")
81
+ )
82
+ assert result.allowed, result
83
+ print("Installed wheel smoke test passed.")
84
+ PY
85
+ - name: Smoke-test optional LangChain middleware extra
86
+ run: |
87
+ python -m venv /tmp/langgraph-secure-agents-extra
88
+ /tmp/langgraph-secure-agents-extra/bin/python -m pip install ".[langchain-agents]"
89
+ /tmp/langgraph-secure-agents-extra/bin/python - <<'PY'
90
+ from langgraph_secure_agents import PolicyEngine, Principal
91
+ from langgraph_secure_agents.middleware import create_authorization_middleware
92
+
93
+ middleware = create_authorization_middleware(PolicyEngine(), lambda context: Principal("smoke-user"))
94
+ assert callable(middleware.wrap_tool_call)
95
+ print("Optional middleware extra smoke test passed.")
96
+ PY
97
+
98
+ release:
99
+ name: Version and prepare release
100
+ if: github.ref == 'refs/heads/main'
101
+ runs-on: ubuntu-latest
102
+ needs: [quality, package-smoke]
103
+ permissions:
104
+ contents: write
105
+ outputs:
106
+ released: ${{ steps.semantic.outputs.released }}
107
+ tag: ${{ steps.semantic.outputs.tag }}
108
+ version: ${{ steps.semantic.outputs.version }}
109
+ steps:
110
+ - name: Check out repository
111
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
112
+ with:
113
+ fetch-depth: 0
114
+ - name: Create semantic version and GitHub Release
115
+ id: semantic
116
+ uses: python-semantic-release/python-semantic-release@b700dbeb1f431a0fcc30a79f3f0869407fe07278 # v10.7.0
117
+ with:
118
+ github_token: ${{ secrets.GITHUB_TOKEN }}
119
+ git_committer_name: "github-actions"
120
+ git_committer_email: "actions@users.noreply.github.com"
121
+ - name: Set up Python for distribution build
122
+ if: steps.semantic.outputs.released == 'true'
123
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
124
+ with:
125
+ python-version: "3.12"
126
+ - name: Build and validate distributions
127
+ if: steps.semantic.outputs.released == 'true'
128
+ run: |
129
+ python -m pip install --upgrade pip build twine
130
+ python -m build
131
+ python -m twine check dist/*
132
+ - name: Upload validated distributions
133
+ if: steps.semantic.outputs.released == 'true'
134
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
135
+ with:
136
+ name: release-dist
137
+ path: dist/
138
+ if-no-files-found: error
139
+ retention-days: 1
140
+ - name: Attach distributions to GitHub Release
141
+ if: steps.semantic.outputs.released == 'true'
142
+ uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
143
+ with:
144
+ tag_name: ${{ steps.semantic.outputs.tag }}
145
+ fail_on_unmatched_files: true
146
+ files: dist/*
147
+
148
+ publish-pypi:
149
+ name: Publish to PyPI using Trusted Publishing
150
+ runs-on: ubuntu-latest
151
+ needs: release
152
+ if: needs.release.outputs.released == 'true'
153
+ environment:
154
+ name: pypi
155
+ url: https://pypi.org/project/langgraph-secure-agents/
156
+ permissions:
157
+ id-token: write
158
+ steps:
159
+ - name: Download validated distributions
160
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
161
+ with:
162
+ name: release-dist
163
+ path: dist/
164
+ - name: Publish distributions to PyPI
165
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
166
+ with:
167
+ packages-dir: dist/
168
+ print-hash: true
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .coverage
8
+ htmlcov/
9
+ dist/
10
+ build/
11
+ .venv/
12
+ venv/
13
+ .env
14
+ .env.*
15
+ !.env.example
16
+ .idea/
17
+ .vscode/
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ Notable user-facing changes are recorded here. This project follows PEP 440 versioning; pre-release versions may include API changes.
4
+
5
+ ## Unreleased
6
+
7
+ - Prepare the first public alpha distribution.
8
+ - Document deterministic policy evaluation, callable protection, optional LangChain/LangGraph middleware, and checkpoint-bound human-approval handling.
9
+ - Explicitly document host-application responsibilities for identity, reviewer authentication, approval consumption, and downstream authorization.
10
+
11
+ ## 0.1.0a0
12
+
13
+ Initial alpha scope:
14
+
15
+ - Immutable authorization models and deterministic policy evaluation.
16
+ - Default-deny and deny-overrides semantics with constrained wildcard selectors.
17
+ - Synchronous and asynchronous Python callable protection.
18
+ - Optional LangChain agent middleware for LangGraph tool execution.
19
+ - Human-approval interrupts with request fingerprints bound to the execution scope and matched policy definitions.
20
+ - Fail-closed behavior for configured audit/resolver errors and malformed approval responses.
21
+ - Threat model, security reporting guidance, model-free example, CI, distribution smoke tests, and automated GitHub/PyPI release workflow.
22
+
23
+ This alpha has not received an independent security audit and is not suitable as the sole security boundary for production or sensitive data.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peyman Salimi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,238 @@
1
+ Metadata-Version: 2.5
2
+ Name: langgraph-secure-agents
3
+ Version: 0.1.0
4
+ Summary: Deterministic access control for LangGraph agents.
5
+ Project-URL: Homepage, https://github.com/peymanpro/LangGraph-SecureAgents
6
+ Project-URL: Repository, https://github.com/peymanpro/LangGraph-SecureAgents
7
+ Project-URL: Issues, https://github.com/peymanpro/LangGraph-SecureAgents/issues
8
+ Project-URL: Security, https://github.com/peymanpro/LangGraph-SecureAgents/blob/main/SECURITY.md
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: access-control,ai-agents,authorization,langgraph,security
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.11
21
+ Provides-Extra: dev
22
+ Requires-Dist: langchain<2.0,>=1.0; extra == 'dev'
23
+ Requires-Dist: langgraph<2.0,>=1.2; extra == 'dev'
24
+ Requires-Dist: mypy>=1.13; extra == 'dev'
25
+ Requires-Dist: pytest-cov>=6.0; extra == 'dev'
26
+ Requires-Dist: pytest>=8.3; extra == 'dev'
27
+ Requires-Dist: pyyaml>=6.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.9; extra == 'dev'
29
+ Provides-Extra: langchain-agents
30
+ Requires-Dist: langchain-core<2.0,>=1.0; extra == 'langchain-agents'
31
+ Requires-Dist: langchain<2.0,>=1.0; extra == 'langchain-agents'
32
+ Requires-Dist: langgraph<2.0,>=1.2; extra == 'langchain-agents'
33
+ Provides-Extra: langgraph
34
+ Requires-Dist: langgraph<2.0,>=1.2; extra == 'langgraph'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # LangGraph-SecureAgents
38
+
39
+ **Deterministic access control for LangGraph-powered agents.**
40
+
41
+ LangGraph-SecureAgents is an experimental Python library for enforcing authorization at agent tool-execution boundaries. Agent orchestration may be handled by LangGraph; authorization decisions are made by trusted application code, never by an LLM.
42
+
43
+ > **Status:** early development. The policy engine, guarded-callable primitive, and optional agent middleware are implemented, but the API is not stable and has not received an independent security audit. Do not use this project to protect production data yet.
44
+
45
+ ## Security principles
46
+
47
+ Read the [threat model](docs/threat-model.md) for trust boundaries and deployment guidance, and [SECURITY.md](SECURITY.md) for vulnerability reporting and security limitations.
48
+
49
+ 1. **Default deny:** no matching allow policy means access is denied.
50
+ 2. **Deny overrides:** an explicit deny rule wins over any matching allow rule.
51
+ 3. **Deterministic authorization:** model output cannot grant permissions.
52
+ 4. **Enforce at execution:** evaluate policy immediately before a protected operation.
53
+ 5. **Trusted identity:** the application must resolve principal identity and roles from authenticated context, never from LLM-controlled tool arguments.
54
+ 6. **Fail closed:** resolver or configured audit failures prevent protected execution.
55
+ 7. **Least privilege:** rules specify a principal or role, action, and resource.
56
+
57
+ ## Installation
58
+
59
+ Requires Python 3.11 or newer.
60
+
61
+ ### Install the published package
62
+
63
+ After the first PyPI release is available, install the alpha package with its optional agent middleware:
64
+
65
+ ```bash
66
+ python -m pip install --pre "langgraph-secure-agents[langchain-agents]"
67
+ ```
68
+
69
+ For the core policy engine and callable guard without LangGraph middleware:
70
+
71
+ ```bash
72
+ python -m pip install --pre langgraph-secure-agents
73
+ ```
74
+
75
+ The `--pre` flag is needed while the latest published version is an alpha pre-release. Until the package is available on PyPI, use the source-install instructions below.
76
+
77
+ ### Install from a source checkout
78
+
79
+ Clone this repository, then install the core package and development tools:
80
+
81
+ ```bash
82
+ python -m pip install -e ".[dev]"
83
+ ```
84
+
85
+ Install the optional middleware dependencies from the same checkout:
86
+
87
+ ```bash
88
+ python -m pip install -e ".[langchain-agents]"
89
+ ```
90
+
91
+ The core policy engine and callable guard do not require LangGraph or an LLM provider.
92
+
93
+ ## Run the model-free LangGraph example
94
+
95
+ The example uses fake payroll records and a trusted runtime context. It needs no model API key:
96
+
97
+ ```bash
98
+ python examples/policy_guarded_workflow.py
99
+ ```
100
+
101
+ It demonstrates an allowed HR request, a denied request for an unapproved resource, and a denied request from a user without the required role.
102
+
103
+ ## Quick start: deterministic policy engine
104
+
105
+ ```python
106
+ from langgraph_secure_agents import (
107
+ AccessRequest,
108
+ DecisionReason,
109
+ Effect,
110
+ PolicyEngine,
111
+ PolicyRule,
112
+ Principal,
113
+ )
114
+
115
+ policy = PolicyEngine(
116
+ [
117
+ PolicyRule(
118
+ rule_id="analysts-read-reports",
119
+ effect=Effect.ALLOW,
120
+ principal_ids=frozenset(),
121
+ roles=frozenset({"analyst"}),
122
+ actions=frozenset({"read"}),
123
+ resources=frozenset({"reports"}),
124
+ )
125
+ ]
126
+ )
127
+
128
+ decision = policy.evaluate(
129
+ AccessRequest(
130
+ principal=Principal("user-123", roles=frozenset({"analyst"})),
131
+ action="read",
132
+ resource="reports",
133
+ )
134
+ )
135
+
136
+ assert decision.allowed
137
+ assert decision.reason is DecisionReason.EXPLICIT_ALLOW
138
+ ```
139
+
140
+ ### Decision outcomes and policy updates
141
+
142
+ `Decision.reason` is a public `DecisionReason` enum with stable machine-readable values: `explicit_allow`, `explicit_deny`, `default_deny`, `approval_required`, `approval_invalid`, `approval_denied`, and `human_approved`. Use the enum constants in application code rather than comparing free-form strings. `matched_rule_ids` identifies the policy rules responsible for a rule-based outcome; a default-deny decision has no matched rule IDs. `requires_approval` is true only for `approval_required`.
143
+
144
+ A `PolicyEngine`'s rules cannot be replaced after construction. To deploy a policy change, construct and validate a new engine, then switch the application to it using the application's own configuration/reload mechanism. Do not mutate private state on a live engine. This matters for approval pauses: the request fingerprint binds approval to the matched rule definitions, so a resumed decision must still refer to the policy that produced the interrupt.
145
+
146
+ ## Protect a Python callable
147
+
148
+ ```python
149
+ from langgraph_secure_agents import PolicyEngine, Principal, protect_callable
150
+
151
+ # This resolver must read a trusted, request-scoped identity source.
152
+ current_principal = lambda: Principal("user-123", roles=frozenset({"analyst"}))
153
+
154
+ protected_lookup = protect_callable(
155
+ lookup_report,
156
+ policy=policy,
157
+ principal_resolver=current_principal,
158
+ action="read",
159
+ resource_resolver=lambda report_id: f"report:{report_id}",
160
+ )
161
+ ```
162
+
163
+ The resource resolver must describe the same resource the wrapped function actually accesses. Resolver callbacks receive detached copies of arguments, and attempts to mutate those copies fail closed before execution. Resolvers must otherwise be trusted, side-effect-free application code. For multi-user applications, the principal resolver must read request-scoped trusted context; do not capture one user's identity in a process-wide shared tool.
164
+
165
+ ## Optional agent middleware
166
+
167
+ The optional module `langgraph_secure_agents.middleware` provides `create_authorization_middleware` for LangChain agents built on LangGraph. It intercepts tool calls before execution. By default, the policy action is `execute` and the resource is the tool name. Custom resolvers can map tool calls to domain-level actions and resources.
168
+
169
+ The middleware receives runtime context from the agent invocation. The application must configure a principal resolver that validates this context and creates a `Principal` from trusted identity data. Custom action/resource resolvers receive detached copies of tool arguments; attempts to mutate these copies fail closed. Denied calls return an error tool message and do not invoke the tool handler.
170
+
171
+ ## Human approval for sensitive actions
172
+
173
+ A policy can explicitly require approval instead of granting access immediately:
174
+
175
+ ```python
176
+ approval_policy = PolicyEngine(
177
+ [
178
+ PolicyRule(
179
+ rule_id="approval-required-payroll-read",
180
+ effect=Effect.REQUIRE_APPROVAL,
181
+ actions=frozenset({"read"}),
182
+ resources=frozenset({"payroll:employee-7"}),
183
+ roles=frozenset({"hr"}),
184
+ )
185
+ ]
186
+ )
187
+ ```
188
+
189
+ When this policy is used with the optional agent middleware, LangGraph pauses at an interrupt before the tool handler executes. The paused payload describes the principal, action, resource, policy rule, tool, and a request fingerprint. After a reviewer makes a decision, the host application resumes the same checkpoint/thread with a value shaped like:
190
+
191
+ ```python
192
+ {
193
+ "approved": True,
194
+ "request_fingerprint": fingerprint_from_interrupt_payload,
195
+ }
196
+ ```
197
+
198
+ The middleware rejects missing, malformed, or mismatched approval responses. The fingerprint binds the response to the current principal, roles, tool call, arguments, action, resource, exact matched policy definitions, and LangGraph execution scope (`thread_id`, `checkpoint_id`, `checkpoint_ns`, and `task_id`). This makes the fingerprint different in another checkpointed execution even when request fields and the tool-call ID are repeated. Approval-required calls need LangGraph 1.2 or newer with an active checkpointer; missing execution metadata fails closed. Fingerprinting rejects non-string JSON object keys, non-finite numbers, cyclic structures, and other non-JSON argument values. **The fingerprint is a deterministic request-binding check, not proof of reviewer identity, permission, or one-time use.** The host application must authenticate the reviewer, authorize who can resume the paused run, and protect the resume endpoint. For side-effecting tools, ensure request IDs are unique per operation and durably consume each approval once. The `approval_validator` callback described below can enforce reviewer validation and atomic consumption at the middleware boundary. An explicit matching deny rule always takes precedence over approval.
199
+
200
+ For deployments that need enforcement in the library boundary, pass an `approval_validator` callback to `create_authorization_middleware`. It receives the resumed approval mapping, the resolved `AccessRequest`, and the request fingerprint. It should authenticate and authorize the reviewer, then atomically claim/consume the fingerprint in a durable store; return the literal `True` only for a valid first use. A false or non-boolean result rejects the approval, and an exception fails closed by preventing tool execution. Implement this callback with your application's identity provider and transactional storage—do not use an in-memory set for multi-process or persistent workloads. The callback is invoked for validly-shaped approval and rejection responses, so the host can consume either human decision once. If the callback is omitted, the host must enforce these controls outside the library. An explicit matching deny rule always takes precedence over approval.
201
+
202
+ ## Validation
203
+
204
+ ```bash
205
+ ruff check .
206
+ ruff format --check .
207
+ mypy src
208
+ pytest
209
+ ```
210
+
211
+ GitHub Actions runs these checks across Python 3.11, 3.12, and 3.13 on pull requests and pushes to `main`, with a 98% minimum line-coverage gate. A separate packaging job builds both a wheel and source distribution, validates metadata, smoke-tests the installed wheel in a clean environment, and installs the `langchain-agents` extra in a separate environment to verify the optional middleware dependencies.
212
+
213
+ After a change is merged into `main`, the automatic release workflow analyzes Conventional Commit messages. If a release-worthy change exists and all release quality checks pass, it updates the version and changelog, creates a version commit and Git tag, builds distributions, creates a GitHub Release, and publishes to PyPI using Trusted Publishing. No manual tag creation is required. See [the release process](docs/releasing.md).
214
+
215
+ ## Roadmap
216
+
217
+ - [x] Typed package foundation, license, and CI
218
+ - [x] Policy models and default-deny evaluation
219
+ - [x] Deny-overrides semantics and constrained wildcards
220
+ - [x] Guarded callable and fail-closed auditing
221
+ - [x] Optional agent tool-call middleware
222
+ - [x] Model-free LangGraph workflow example with mock payroll data
223
+ - [ ] Example conversational agent using a model provider
224
+ - [x] Human-approval interrupt integration for selected sensitive actions
225
+ - [x] Initial threat model and security reporting guidance
226
+ - [ ] API review and release-readiness checks
227
+
228
+ ## Changelog
229
+
230
+ See [CHANGELOG.md](CHANGELOG.md) for user-facing changes and release notes.
231
+
232
+ ## License
233
+
234
+ MIT. See [LICENSE](LICENSE).
235
+
236
+ ## Disclaimer
237
+
238
+ This project is experimental and is not a substitute for a complete application security review. Integrators remain responsible for authenticating principals, validating trusted context, enforcing tenant boundaries, and protecting downstream resources.