yui-agent-guard 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.
- yui_agent_guard-0.1.0/.github/workflows/ci.yml +65 -0
- yui_agent_guard-0.1.0/.github/workflows/release.yml +84 -0
- yui_agent_guard-0.1.0/.gitignore +8 -0
- yui_agent_guard-0.1.0/PKG-INFO +277 -0
- yui_agent_guard-0.1.0/README.md +248 -0
- yui_agent_guard-0.1.0/examples/ai_resilience_path_policy.yaml +32 -0
- yui_agent_guard-0.1.0/examples/architecture_policy.yaml +20 -0
- yui_agent_guard-0.1.0/examples/content_security_policy.yaml +19 -0
- yui_agent_guard-0.1.0/pyproject.toml +66 -0
- yui_agent_guard-0.1.0/scripts/check_pypi_release_state.py +65 -0
- yui_agent_guard-0.1.0/scripts/check_release_version.py +46 -0
- yui_agent_guard-0.1.0/src/agent_guard/__init__.py +27 -0
- yui_agent_guard-0.1.0/src/agent_guard/api_guard.py +137 -0
- yui_agent_guard-0.1.0/src/agent_guard/cli.py +255 -0
- yui_agent_guard-0.1.0/src/agent_guard/content_guard.py +343 -0
- yui_agent_guard-0.1.0/src/agent_guard/digest_guard.py +132 -0
- yui_agent_guard-0.1.0/src/agent_guard/path_guard.py +182 -0
- yui_agent_guard-0.1.0/src/agent_guard/py.typed +0 -0
- yui_agent_guard-0.1.0/tests/test_api_guard.py +121 -0
- yui_agent_guard-0.1.0/tests/test_check_pypi_release_state.py +48 -0
- yui_agent_guard-0.1.0/tests/test_check_release_version.py +53 -0
- yui_agent_guard-0.1.0/tests/test_cli.py +208 -0
- yui_agent_guard-0.1.0/tests/test_content_guard.py +266 -0
- yui_agent_guard-0.1.0/tests/test_digest_guard.py +142 -0
- yui_agent_guard-0.1.0/tests/test_packaging.py +31 -0
- yui_agent_guard-0.1.0/tests/test_path_guard.py +121 -0
- yui_agent_guard-0.1.0/tests/test_public_api.py +20 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Where: .github/workflows/ci.yml
|
|
2
|
+
# What: run workflow linting, tests, and packaging checks on every push to master and every PR.
|
|
3
|
+
# Why: keep the extracted guard package safe while the API surface is still moving.
|
|
4
|
+
|
|
5
|
+
name: ci
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: [master]
|
|
13
|
+
pull_request:
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
actionlint:
|
|
17
|
+
name: actionlint
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v6
|
|
21
|
+
- uses: devops-actions/actionlint@v0.1.11
|
|
22
|
+
|
|
23
|
+
test:
|
|
24
|
+
name: pytest (py${{ matrix.python-version }})
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
strategy:
|
|
27
|
+
fail-fast: false
|
|
28
|
+
matrix:
|
|
29
|
+
python-version: ['3.11', '3.12']
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v6
|
|
32
|
+
- uses: actions/setup-python@v6
|
|
33
|
+
with:
|
|
34
|
+
python-version: ${{ matrix.python-version }}
|
|
35
|
+
- name: Install package + dev deps
|
|
36
|
+
run: pip install -e ".[dev]"
|
|
37
|
+
- name: Run tests
|
|
38
|
+
run: pytest -q
|
|
39
|
+
- name: Run CLI smoke tests
|
|
40
|
+
run: |
|
|
41
|
+
python -m agent_guard.cli content check --repo-root . --policy examples/content_security_policy.yaml --mode registered --scan-dir . --json
|
|
42
|
+
python -m agent_guard.cli path check --root . --policy examples/ai_resilience_path_policy.yaml --json
|
|
43
|
+
mkdir -p /tmp/agent-guard-digest-smoke
|
|
44
|
+
printf 'pinned\n' > /tmp/agent-guard-digest-smoke/pinned.txt
|
|
45
|
+
python - <<'PY'
|
|
46
|
+
import hashlib
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
|
|
49
|
+
root = Path("/tmp/agent-guard-digest-smoke")
|
|
50
|
+
digest = hashlib.sha256((root / "pinned.txt").read_bytes()).hexdigest()
|
|
51
|
+
(root / "policy.yaml").write_text(
|
|
52
|
+
"checks:\n"
|
|
53
|
+
" - id: pinned_text\n"
|
|
54
|
+
" path: pinned.txt\n"
|
|
55
|
+
f" sha256: '{digest}'\n",
|
|
56
|
+
encoding="utf-8",
|
|
57
|
+
)
|
|
58
|
+
PY
|
|
59
|
+
python -m agent_guard.cli digest check --root /tmp/agent-guard-digest-smoke --policy /tmp/agent-guard-digest-smoke/policy.yaml --json
|
|
60
|
+
- name: Build sdist + wheel
|
|
61
|
+
if: matrix.python-version == '3.12'
|
|
62
|
+
run: python -m build
|
|
63
|
+
- name: Verify metadata (twine check)
|
|
64
|
+
if: matrix.python-version == '3.12'
|
|
65
|
+
run: python -m twine check dist/*
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Where: .github/workflows/release.yml
|
|
2
|
+
# What: build sdist+wheel and publish to PyPI on tag push, via Trusted Publishing.
|
|
3
|
+
# Why: make agent-guard consumable from downstream CI without long-lived PyPI tokens.
|
|
4
|
+
|
|
5
|
+
name: release
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
inputs:
|
|
13
|
+
publish:
|
|
14
|
+
description: 'Publish to PyPI instead of build-only dry run'
|
|
15
|
+
required: true
|
|
16
|
+
type: boolean
|
|
17
|
+
default: false
|
|
18
|
+
push:
|
|
19
|
+
tags:
|
|
20
|
+
- 'v*'
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
validate-release-request:
|
|
27
|
+
name: validate release request
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- name: Validate manual publish ref
|
|
31
|
+
run: |
|
|
32
|
+
set -euo pipefail
|
|
33
|
+
if [ "${{ github.event_name }}" = "workflow_dispatch" ] \
|
|
34
|
+
&& [ "${{ inputs.publish }}" = "true" ] \
|
|
35
|
+
&& [[ "${GITHUB_REF}" != refs/tags/v* ]]; then
|
|
36
|
+
echo "::error::manual publish=true must be run against a v* tag ref, not ${GITHUB_REF}"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
echo "release request accepted for ${GITHUB_REF}"
|
|
40
|
+
|
|
41
|
+
build:
|
|
42
|
+
name: build sdist + wheel
|
|
43
|
+
needs: validate-release-request
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v6
|
|
47
|
+
- uses: actions/setup-python@v6
|
|
48
|
+
with:
|
|
49
|
+
python-version: '3.12'
|
|
50
|
+
- name: Assert tag matches package version
|
|
51
|
+
if: github.event_name == 'push' || startsWith(github.ref, 'refs/tags/v')
|
|
52
|
+
env:
|
|
53
|
+
TAG_NAME: ${{ github.ref_name }}
|
|
54
|
+
run: python scripts/check_release_version.py "$TAG_NAME"
|
|
55
|
+
- name: Check PyPI release state
|
|
56
|
+
if: github.event_name == 'push' || startsWith(github.ref, 'refs/tags/v')
|
|
57
|
+
run: python scripts/check_pypi_release_state.py
|
|
58
|
+
- name: Install build + twine
|
|
59
|
+
run: python -m pip install --upgrade build twine
|
|
60
|
+
- name: Build distributions
|
|
61
|
+
run: python -m build
|
|
62
|
+
- name: Verify metadata (twine check)
|
|
63
|
+
run: python -m twine check dist/*
|
|
64
|
+
- uses: actions/upload-artifact@v7
|
|
65
|
+
with:
|
|
66
|
+
name: dist
|
|
67
|
+
path: dist/
|
|
68
|
+
|
|
69
|
+
publish:
|
|
70
|
+
name: publish to PyPI (OIDC)
|
|
71
|
+
needs: build
|
|
72
|
+
if: github.event_name == 'push' || (inputs.publish && startsWith(github.ref, 'refs/tags/v'))
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
environment:
|
|
75
|
+
name: pypi
|
|
76
|
+
url: https://pypi.org/p/yui-agent-guard
|
|
77
|
+
permissions:
|
|
78
|
+
id-token: write
|
|
79
|
+
steps:
|
|
80
|
+
- uses: actions/download-artifact@v8
|
|
81
|
+
with:
|
|
82
|
+
name: dist
|
|
83
|
+
path: dist/
|
|
84
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yui-agent-guard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Static repository guardrails for agent-touched codebases.
|
|
5
|
+
Project-URL: Repository, https://github.com/yui-stingray/agent-guard
|
|
6
|
+
Project-URL: Issues, https://github.com/yui-stingray/agent-guard/issues
|
|
7
|
+
Author: yui-stingray
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: agent,ai-agents,guardrails,policy,security
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: pyyaml<7,>=6
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build<2,>=1.2; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-cov<7,>=5; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest<9,>=8; extra == 'dev'
|
|
27
|
+
Requires-Dist: twine<7,>=6; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# agent-guard
|
|
31
|
+
|
|
32
|
+
> Static repository guardrails for agent-touched codebases.
|
|
33
|
+
>
|
|
34
|
+
> `agent-policy` decides whether an agent should do something.
|
|
35
|
+
> `agent-guard` checks whether the repository content still obeys the rules.
|
|
36
|
+
|
|
37
|
+
**Status**: `0.1.0` alpha. The current MVP ships four scanners: `api`, `content`, `path`, and `digest`.
|
|
38
|
+
|
|
39
|
+
## Why
|
|
40
|
+
|
|
41
|
+
`agent-guard` exists to enforce fail-closed static checks around agent-operated repositories without pulling in a full control plane.
|
|
42
|
+
|
|
43
|
+
The current extracted scanners are intentionally narrow:
|
|
44
|
+
- `api`: scan repository text files for URLs, allow approved API patterns, fail on forbidden API patterns
|
|
45
|
+
- `content`: scan Markdown or other configured text files for dangerous instruction patterns
|
|
46
|
+
- `path`: scan repository path names for private artifacts, env files, and other publish-time leaks
|
|
47
|
+
- `digest`: verify SHA-256 pins for governance docs and safety-critical scripts
|
|
48
|
+
- return stable JSON or text output for local hooks and CI
|
|
49
|
+
|
|
50
|
+
It does **not** manage approvals, logs, state, or UI. Those belong in higher layers.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install -e .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Requires Python 3.11+. The only runtime dependency is `PyYAML`.
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
API surface guard:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
agent-guard api check --root . --policy examples/architecture_policy.yaml
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Content security guard:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
agent-guard content check --repo-root . --policy examples/content_security_policy.yaml --mode registered --scan-dir skills
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Path-name guard:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
agent-guard path check --root . --policy examples/ai_resilience_path_policy.yaml
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Digest guard:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
agent-guard digest check --root . --policy digest_policy.yaml
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
JSON mode is stable and intended for CI/wrappers:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
agent-guard api check --root . --policy examples/architecture_policy.yaml --json
|
|
90
|
+
agent-guard content check --repo-root . --policy examples/content_security_policy.yaml --mode registered --scan-dir skills --json
|
|
91
|
+
agent-guard path check --root . --policy examples/ai_resilience_path_policy.yaml --json
|
|
92
|
+
agent-guard digest check --root . --policy digest_policy.yaml --json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Current scanners
|
|
96
|
+
|
|
97
|
+
### API guard
|
|
98
|
+
|
|
99
|
+
The API guard scans configured paths for URLs and compares them against allow/deny regex lists.
|
|
100
|
+
|
|
101
|
+
Typical use case:
|
|
102
|
+
- keep a CLI-first repository from silently drifting into direct inference API calls
|
|
103
|
+
|
|
104
|
+
It returns:
|
|
105
|
+
- exit `0` on clean
|
|
106
|
+
- exit `1` on violation
|
|
107
|
+
- exit `2` on configuration/runtime error
|
|
108
|
+
|
|
109
|
+
### Content guard
|
|
110
|
+
|
|
111
|
+
The content guard scans configured text content for forbidden regex patterns.
|
|
112
|
+
|
|
113
|
+
Supported modes:
|
|
114
|
+
- `registered`: scan a configured directory under the repo
|
|
115
|
+
- `preregister`: scan explicit file or directory targets
|
|
116
|
+
- `new`: scan changed files from git diff, optionally including untracked files
|
|
117
|
+
|
|
118
|
+
`new` mode uses two behaviors: with `--since-ref`, it scans files changed between that ref and `HEAD`; without `--since-ref`, it scans the current working tree diff and can optionally include untracked files.
|
|
119
|
+
|
|
120
|
+
Typical use cases:
|
|
121
|
+
- keep dangerous install instructions out of skills docs
|
|
122
|
+
- block hardcoded credential-like strings in agent-authored Markdown
|
|
123
|
+
- catch destructive command suggestions before they spread
|
|
124
|
+
|
|
125
|
+
It returns:
|
|
126
|
+
- exit `0` on clean
|
|
127
|
+
- exit `1` on violation
|
|
128
|
+
- exit `2` on configuration/runtime error
|
|
129
|
+
|
|
130
|
+
### Path guard
|
|
131
|
+
|
|
132
|
+
The path guard scans file and directory names under configured roots. It uses
|
|
133
|
+
allowlist-first matching so narrow exceptions such as `.env.example` can be
|
|
134
|
+
allowed while broader deny patterns still block `.env`, `.env.local`, and
|
|
135
|
+
`.env.evil`.
|
|
136
|
+
|
|
137
|
+
Typical use cases:
|
|
138
|
+
- keep `artifacts/private/` out of publishable repository paths
|
|
139
|
+
- block bypass corpus files and red-team session logs by name
|
|
140
|
+
- catch env-file leaks even when contents are ignored or unreadable
|
|
141
|
+
|
|
142
|
+
It returns:
|
|
143
|
+
- exit `0` on clean
|
|
144
|
+
- exit `1` on violation
|
|
145
|
+
- exit `2` on configuration/runtime error
|
|
146
|
+
|
|
147
|
+
### Digest guard
|
|
148
|
+
|
|
149
|
+
The digest guard verifies pinned SHA-256 values for files that should not
|
|
150
|
+
drift silently. Each check names a repository-relative path, an expected
|
|
151
|
+
digest, and an optional `start_line` when only the content body should be
|
|
152
|
+
hashed.
|
|
153
|
+
|
|
154
|
+
Typical use cases:
|
|
155
|
+
- detect unreviewed edits to governance documents
|
|
156
|
+
- pin verifier scripts that protect publication or release gates
|
|
157
|
+
- preserve B9-style constitution integrity checks without shell-specific logic
|
|
158
|
+
|
|
159
|
+
It returns:
|
|
160
|
+
- exit `0` on clean
|
|
161
|
+
- exit `1` on violation
|
|
162
|
+
- exit `2` on configuration/runtime error
|
|
163
|
+
|
|
164
|
+
## Example policies
|
|
165
|
+
|
|
166
|
+
### API guard policy
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
scan:
|
|
170
|
+
include:
|
|
171
|
+
- src
|
|
172
|
+
- scripts
|
|
173
|
+
exclude:
|
|
174
|
+
- scripts/build_instructions.sh
|
|
175
|
+
|
|
176
|
+
policy:
|
|
177
|
+
allowed_api_patterns:
|
|
178
|
+
- "^https://ntfy\.sh/"
|
|
179
|
+
forbidden_api_patterns:
|
|
180
|
+
- "^https://api\.openai\.com/"
|
|
181
|
+
- "^https://api\.anthropic\.com/"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
A ready-to-run copy lives in [`examples/architecture_policy.yaml`](examples/architecture_policy.yaml).
|
|
185
|
+
|
|
186
|
+
### Content guard policy
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
file_globs:
|
|
190
|
+
- "**/*.md"
|
|
191
|
+
exclude_globs:
|
|
192
|
+
- "archive/**"
|
|
193
|
+
forbidden_patterns:
|
|
194
|
+
- id: pipe_to_shell
|
|
195
|
+
severity: high
|
|
196
|
+
pattern: '(?i)curl\s+[^\n|]+\|\s*(bash|sh)\b'
|
|
197
|
+
message: "pipe-to-shell pattern is forbidden"
|
|
198
|
+
exclude_globs:
|
|
199
|
+
- "fixtures/red-team/**"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
A ready-to-run copy lives in [`examples/content_security_policy.yaml`](examples/content_security_policy.yaml).
|
|
203
|
+
|
|
204
|
+
Content rules may define per-rule `include_globs` / `exclude_globs`. Use this
|
|
205
|
+
when a repository contains intentional adversarial fixtures that should stay
|
|
206
|
+
scannable for secrets but should not fail dangerous-command rules. For narrow
|
|
207
|
+
documented examples, append an inline suppression such as
|
|
208
|
+
`# agent-guard: allow pipe_to_shell` or `# agent-guard: allow all` on the same
|
|
209
|
+
line.
|
|
210
|
+
|
|
211
|
+
### Path guard policy
|
|
212
|
+
|
|
213
|
+
```yaml
|
|
214
|
+
scan:
|
|
215
|
+
include:
|
|
216
|
+
- "."
|
|
217
|
+
exclude:
|
|
218
|
+
- ".git"
|
|
219
|
+
- ".venv"
|
|
220
|
+
- "node_modules"
|
|
221
|
+
|
|
222
|
+
policy:
|
|
223
|
+
allowed_path_patterns:
|
|
224
|
+
- "(^|/)\\.env\\.example$"
|
|
225
|
+
forbidden_path_patterns:
|
|
226
|
+
- id: private_artifacts
|
|
227
|
+
severity: high
|
|
228
|
+
pattern: "(^|/)artifacts/private(/|$)"
|
|
229
|
+
message: "private artifact directory must stay outside published/tracked paths"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
A ready-to-run ai-resilience-style copy lives in
|
|
233
|
+
[`examples/ai_resilience_path_policy.yaml`](examples/ai_resilience_path_policy.yaml).
|
|
234
|
+
|
|
235
|
+
### Digest guard policy
|
|
236
|
+
|
|
237
|
+
```yaml
|
|
238
|
+
checks:
|
|
239
|
+
- id: constitution_full
|
|
240
|
+
path: agent-constitution-v0.md
|
|
241
|
+
sha256: "<64-char lowercase sha256>"
|
|
242
|
+
- id: constitution_content
|
|
243
|
+
path: agent-constitution-v0.md
|
|
244
|
+
sha256: "<64-char lowercase sha256>"
|
|
245
|
+
start_line: 15
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## CLI
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
agent-guard api check --root <repo> --policy <yaml> [--json]
|
|
252
|
+
agent-guard content check --repo-root <repo> --policy <yaml> --mode <registered|preregister|new> [--scan-dir <dir>] [--targets <paths...>] [--since-ref <ref>] [--no-untracked] [--json]
|
|
253
|
+
agent-guard path check --root <repo> --policy <yaml> [--json]
|
|
254
|
+
agent-guard digest check --root <repo> --policy <yaml> [--json]
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Roadmap
|
|
258
|
+
|
|
259
|
+
Planned next steps:
|
|
260
|
+
- shared result envelope helpers across scanners
|
|
261
|
+
- optional pre-commit examples
|
|
262
|
+
|
|
263
|
+
## Releases
|
|
264
|
+
|
|
265
|
+
Tag-driven. Pushing a `vX.Y.Z` annotated tag triggers
|
|
266
|
+
[`.github/workflows/release.yml`](.github/workflows/release.yml), which first
|
|
267
|
+
verifies that the tag matches `[project].version` in `pyproject.toml`, checks
|
|
268
|
+
that the version is not already present on PyPI, then builds the sdist + wheel
|
|
269
|
+
and publishes to PyPI via Trusted Publishing (OIDC). No maintainer-side PyPI
|
|
270
|
+
token is required once the PyPI project environment is configured. Manual
|
|
271
|
+
`workflow_dispatch` with `publish=false` is a build-only dry run; it skips the
|
|
272
|
+
publish job. Manual `publish=true` must be run against a `v*` tag ref; running
|
|
273
|
+
it from a branch fails before build.
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
MIT.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# agent-guard
|
|
2
|
+
|
|
3
|
+
> Static repository guardrails for agent-touched codebases.
|
|
4
|
+
>
|
|
5
|
+
> `agent-policy` decides whether an agent should do something.
|
|
6
|
+
> `agent-guard` checks whether the repository content still obeys the rules.
|
|
7
|
+
|
|
8
|
+
**Status**: `0.1.0` alpha. The current MVP ships four scanners: `api`, `content`, `path`, and `digest`.
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
`agent-guard` exists to enforce fail-closed static checks around agent-operated repositories without pulling in a full control plane.
|
|
13
|
+
|
|
14
|
+
The current extracted scanners are intentionally narrow:
|
|
15
|
+
- `api`: scan repository text files for URLs, allow approved API patterns, fail on forbidden API patterns
|
|
16
|
+
- `content`: scan Markdown or other configured text files for dangerous instruction patterns
|
|
17
|
+
- `path`: scan repository path names for private artifacts, env files, and other publish-time leaks
|
|
18
|
+
- `digest`: verify SHA-256 pins for governance docs and safety-critical scripts
|
|
19
|
+
- return stable JSON or text output for local hooks and CI
|
|
20
|
+
|
|
21
|
+
It does **not** manage approvals, logs, state, or UI. Those belong in higher layers.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install -e .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires Python 3.11+. The only runtime dependency is `PyYAML`.
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
API surface guard:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
agent-guard api check --root . --policy examples/architecture_policy.yaml
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Content security guard:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
agent-guard content check --repo-root . --policy examples/content_security_policy.yaml --mode registered --scan-dir skills
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Path-name guard:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
agent-guard path check --root . --policy examples/ai_resilience_path_policy.yaml
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Digest guard:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
agent-guard digest check --root . --policy digest_policy.yaml
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
JSON mode is stable and intended for CI/wrappers:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
agent-guard api check --root . --policy examples/architecture_policy.yaml --json
|
|
61
|
+
agent-guard content check --repo-root . --policy examples/content_security_policy.yaml --mode registered --scan-dir skills --json
|
|
62
|
+
agent-guard path check --root . --policy examples/ai_resilience_path_policy.yaml --json
|
|
63
|
+
agent-guard digest check --root . --policy digest_policy.yaml --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Current scanners
|
|
67
|
+
|
|
68
|
+
### API guard
|
|
69
|
+
|
|
70
|
+
The API guard scans configured paths for URLs and compares them against allow/deny regex lists.
|
|
71
|
+
|
|
72
|
+
Typical use case:
|
|
73
|
+
- keep a CLI-first repository from silently drifting into direct inference API calls
|
|
74
|
+
|
|
75
|
+
It returns:
|
|
76
|
+
- exit `0` on clean
|
|
77
|
+
- exit `1` on violation
|
|
78
|
+
- exit `2` on configuration/runtime error
|
|
79
|
+
|
|
80
|
+
### Content guard
|
|
81
|
+
|
|
82
|
+
The content guard scans configured text content for forbidden regex patterns.
|
|
83
|
+
|
|
84
|
+
Supported modes:
|
|
85
|
+
- `registered`: scan a configured directory under the repo
|
|
86
|
+
- `preregister`: scan explicit file or directory targets
|
|
87
|
+
- `new`: scan changed files from git diff, optionally including untracked files
|
|
88
|
+
|
|
89
|
+
`new` mode uses two behaviors: with `--since-ref`, it scans files changed between that ref and `HEAD`; without `--since-ref`, it scans the current working tree diff and can optionally include untracked files.
|
|
90
|
+
|
|
91
|
+
Typical use cases:
|
|
92
|
+
- keep dangerous install instructions out of skills docs
|
|
93
|
+
- block hardcoded credential-like strings in agent-authored Markdown
|
|
94
|
+
- catch destructive command suggestions before they spread
|
|
95
|
+
|
|
96
|
+
It returns:
|
|
97
|
+
- exit `0` on clean
|
|
98
|
+
- exit `1` on violation
|
|
99
|
+
- exit `2` on configuration/runtime error
|
|
100
|
+
|
|
101
|
+
### Path guard
|
|
102
|
+
|
|
103
|
+
The path guard scans file and directory names under configured roots. It uses
|
|
104
|
+
allowlist-first matching so narrow exceptions such as `.env.example` can be
|
|
105
|
+
allowed while broader deny patterns still block `.env`, `.env.local`, and
|
|
106
|
+
`.env.evil`.
|
|
107
|
+
|
|
108
|
+
Typical use cases:
|
|
109
|
+
- keep `artifacts/private/` out of publishable repository paths
|
|
110
|
+
- block bypass corpus files and red-team session logs by name
|
|
111
|
+
- catch env-file leaks even when contents are ignored or unreadable
|
|
112
|
+
|
|
113
|
+
It returns:
|
|
114
|
+
- exit `0` on clean
|
|
115
|
+
- exit `1` on violation
|
|
116
|
+
- exit `2` on configuration/runtime error
|
|
117
|
+
|
|
118
|
+
### Digest guard
|
|
119
|
+
|
|
120
|
+
The digest guard verifies pinned SHA-256 values for files that should not
|
|
121
|
+
drift silently. Each check names a repository-relative path, an expected
|
|
122
|
+
digest, and an optional `start_line` when only the content body should be
|
|
123
|
+
hashed.
|
|
124
|
+
|
|
125
|
+
Typical use cases:
|
|
126
|
+
- detect unreviewed edits to governance documents
|
|
127
|
+
- pin verifier scripts that protect publication or release gates
|
|
128
|
+
- preserve B9-style constitution integrity checks without shell-specific logic
|
|
129
|
+
|
|
130
|
+
It returns:
|
|
131
|
+
- exit `0` on clean
|
|
132
|
+
- exit `1` on violation
|
|
133
|
+
- exit `2` on configuration/runtime error
|
|
134
|
+
|
|
135
|
+
## Example policies
|
|
136
|
+
|
|
137
|
+
### API guard policy
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
scan:
|
|
141
|
+
include:
|
|
142
|
+
- src
|
|
143
|
+
- scripts
|
|
144
|
+
exclude:
|
|
145
|
+
- scripts/build_instructions.sh
|
|
146
|
+
|
|
147
|
+
policy:
|
|
148
|
+
allowed_api_patterns:
|
|
149
|
+
- "^https://ntfy\.sh/"
|
|
150
|
+
forbidden_api_patterns:
|
|
151
|
+
- "^https://api\.openai\.com/"
|
|
152
|
+
- "^https://api\.anthropic\.com/"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A ready-to-run copy lives in [`examples/architecture_policy.yaml`](examples/architecture_policy.yaml).
|
|
156
|
+
|
|
157
|
+
### Content guard policy
|
|
158
|
+
|
|
159
|
+
```yaml
|
|
160
|
+
file_globs:
|
|
161
|
+
- "**/*.md"
|
|
162
|
+
exclude_globs:
|
|
163
|
+
- "archive/**"
|
|
164
|
+
forbidden_patterns:
|
|
165
|
+
- id: pipe_to_shell
|
|
166
|
+
severity: high
|
|
167
|
+
pattern: '(?i)curl\s+[^\n|]+\|\s*(bash|sh)\b'
|
|
168
|
+
message: "pipe-to-shell pattern is forbidden"
|
|
169
|
+
exclude_globs:
|
|
170
|
+
- "fixtures/red-team/**"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
A ready-to-run copy lives in [`examples/content_security_policy.yaml`](examples/content_security_policy.yaml).
|
|
174
|
+
|
|
175
|
+
Content rules may define per-rule `include_globs` / `exclude_globs`. Use this
|
|
176
|
+
when a repository contains intentional adversarial fixtures that should stay
|
|
177
|
+
scannable for secrets but should not fail dangerous-command rules. For narrow
|
|
178
|
+
documented examples, append an inline suppression such as
|
|
179
|
+
`# agent-guard: allow pipe_to_shell` or `# agent-guard: allow all` on the same
|
|
180
|
+
line.
|
|
181
|
+
|
|
182
|
+
### Path guard policy
|
|
183
|
+
|
|
184
|
+
```yaml
|
|
185
|
+
scan:
|
|
186
|
+
include:
|
|
187
|
+
- "."
|
|
188
|
+
exclude:
|
|
189
|
+
- ".git"
|
|
190
|
+
- ".venv"
|
|
191
|
+
- "node_modules"
|
|
192
|
+
|
|
193
|
+
policy:
|
|
194
|
+
allowed_path_patterns:
|
|
195
|
+
- "(^|/)\\.env\\.example$"
|
|
196
|
+
forbidden_path_patterns:
|
|
197
|
+
- id: private_artifacts
|
|
198
|
+
severity: high
|
|
199
|
+
pattern: "(^|/)artifacts/private(/|$)"
|
|
200
|
+
message: "private artifact directory must stay outside published/tracked paths"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
A ready-to-run ai-resilience-style copy lives in
|
|
204
|
+
[`examples/ai_resilience_path_policy.yaml`](examples/ai_resilience_path_policy.yaml).
|
|
205
|
+
|
|
206
|
+
### Digest guard policy
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
checks:
|
|
210
|
+
- id: constitution_full
|
|
211
|
+
path: agent-constitution-v0.md
|
|
212
|
+
sha256: "<64-char lowercase sha256>"
|
|
213
|
+
- id: constitution_content
|
|
214
|
+
path: agent-constitution-v0.md
|
|
215
|
+
sha256: "<64-char lowercase sha256>"
|
|
216
|
+
start_line: 15
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## CLI
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
agent-guard api check --root <repo> --policy <yaml> [--json]
|
|
223
|
+
agent-guard content check --repo-root <repo> --policy <yaml> --mode <registered|preregister|new> [--scan-dir <dir>] [--targets <paths...>] [--since-ref <ref>] [--no-untracked] [--json]
|
|
224
|
+
agent-guard path check --root <repo> --policy <yaml> [--json]
|
|
225
|
+
agent-guard digest check --root <repo> --policy <yaml> [--json]
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Roadmap
|
|
229
|
+
|
|
230
|
+
Planned next steps:
|
|
231
|
+
- shared result envelope helpers across scanners
|
|
232
|
+
- optional pre-commit examples
|
|
233
|
+
|
|
234
|
+
## Releases
|
|
235
|
+
|
|
236
|
+
Tag-driven. Pushing a `vX.Y.Z` annotated tag triggers
|
|
237
|
+
[`.github/workflows/release.yml`](.github/workflows/release.yml), which first
|
|
238
|
+
verifies that the tag matches `[project].version` in `pyproject.toml`, checks
|
|
239
|
+
that the version is not already present on PyPI, then builds the sdist + wheel
|
|
240
|
+
and publishes to PyPI via Trusted Publishing (OIDC). No maintainer-side PyPI
|
|
241
|
+
token is required once the PyPI project environment is configured. Manual
|
|
242
|
+
`workflow_dispatch` with `publish=false` is a build-only dry run; it skips the
|
|
243
|
+
publish job. Manual `publish=true` must be run against a `v*` tag ref; running
|
|
244
|
+
it from a branch fails before build.
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT.
|