Skip to content

Commit 4ea5640

Browse files
gatewaybuddyclaude
andcommitted
Add launch kit: PyPI packaging, CI, demo, README badges, repo hygiene
- pyproject.toml: PEP 621 package `agentpier-airlock`, console script `airlock-mcp`, Python >=3.10, Apache-2.0, classifiers - airlock/server.py: add main() entry point for console_scripts - airlock/py.typed: PEP 561 typed package marker - .github/workflows/ci.yml: Python 3.10/3.11/3.12 matrix on push + PR - examples/demo.py: runnable before/after demo (22 findings, synthetic fixtures) - README.md: badges (CI, license, Python, PyPI), Quickstart, embedded demo output - CHANGELOG.md: 0.1.0 initial release notes - .github/ISSUE_TEMPLATE/bug_report.md + feature_request.md - .github/PULL_REQUEST_TEMPLATE.md Build confirmed: agentpier_airlock-0.1.0.tar.gz + agentpier_airlock-0.1.0-py3-none-any.whl Tests: 69/69 green. Leak grep: zero hits on all real identifiers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b94d1fb commit 4ea5640

10 files changed

Lines changed: 422 additions & 12 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
name: Bug report
3+
about: Something isn't working
4+
title: "[BUG] "
5+
labels: bug
6+
assignees: ''
7+
---
8+
9+
## What happened
10+
11+
<!-- Describe what went wrong. -->
12+
13+
## What you expected
14+
15+
<!-- What should have happened instead? -->
16+
17+
## Reproducer
18+
19+
```python
20+
# Minimal code to reproduce the problem
21+
from airlock.scrubber import scrub, gate
22+
23+
text = "..."
24+
print(gate(text))
25+
```
26+
27+
## Environment
28+
29+
- Python version: <!-- e.g. 3.12.3 -->
30+
- airlock version: <!-- e.g. 0.1.0 or commit hash -->
31+
- OS: <!-- e.g. Ubuntu 22.04, macOS 14 -->
32+
33+
## Additional context
34+
35+
<!-- Paste relevant output, tracebacks, or gate() findings here. -->
36+
<!-- IMPORTANT: Do not include real credentials, IPs, account IDs, or hostnames. -->
37+
<!-- Use RFC-reserved values (203.0.113.x, example.com, 123456789012) instead. -->
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
name: Feature request
3+
about: New rule, improvement, or integration idea
4+
title: "[FEATURE] "
5+
labels: enhancement
6+
assignees: ''
7+
---
8+
9+
## What problem does this solve?
10+
11+
<!-- What sensitive data is currently not caught, or what workflow is painful? -->
12+
13+
## Proposed solution
14+
15+
<!-- Describe the change. For a new rule: what pattern, what placeholder, what test fixtures? -->
16+
17+
## Example
18+
19+
```python
20+
# Input that should be caught (use RFC-reserved / documented values only):
21+
text = "some text with 203.0.113.99 in it"
22+
23+
# Expected after fix:
24+
# gate(text)["clean"] == False
25+
# scrub(text) == "some text with <PUBLIC_IP> in it"
26+
```
27+
28+
## False positive risk
29+
30+
<!-- Could this rule accidentally redact legitimate text? Give an example if so. -->
31+
32+
## Alternatives considered
33+
34+
<!-- Any other approaches you considered? -->

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
## What this PR does
2+
3+
<!-- One paragraph summary. Link to the issue it closes if applicable. -->
4+
5+
Closes #
6+
7+
## Type of change
8+
9+
- [ ] Bug fix (new rule or false-positive correction)
10+
- [ ] New rule (adds a new category of sensitive data)
11+
- [ ] Enhancement (improves an existing rule or the MCP server)
12+
- [ ] Docs / examples
13+
14+
## Checklist
15+
16+
- [ ] Tests added for all new behaviour (see CONTRIBUTING.md for fixture conventions)
17+
- [ ] No real credentials, IPs, account IDs, or hostnames in tests — RFC-reserved values only
18+
- [ ] Full test suite passes: `PYTHONPATH=. pytest airlock/tests/ -v`
19+
- [ ] `scrubber.py` docstring updated if the rule list changed
20+
- [ ] No new external dependencies
21+
22+
## Demo (optional)
23+
24+
<!-- Paste `python examples/demo.py` output or a short before/after snippet showing the change. -->

.github/workflows/ci.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
9+
jobs:
10+
test:
11+
name: Test (Python ${{ matrix.python-version }})
12+
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
python-version: ["3.10", "3.11", "3.12"]
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Set up Python ${{ matrix.python-version }}
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: ${{ matrix.python-version }}
25+
26+
- name: Install package
27+
run: pip install -e .
28+
29+
- name: Run tests
30+
run: |
31+
PYTHONPATH=. python -m pytest airlock/tests/ -v

CHANGELOG.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Changelog
2+
3+
All notable changes to Airlock are documented here.
4+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5+
Airlock uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
---
8+
9+
## [0.1.0] — 2026-06-25
10+
11+
Initial public release.
12+
13+
### Added
14+
15+
**Redaction engine (`airlock/scrubber.py`)**
16+
- `scrub(text)` — redacts all sensitive tokens in one pass, returns clean string with typed placeholders
17+
- `gate(text)` — DEFAULT-DENY: returns `{clean, findings, redacted}`; `clean=False` on any finding
18+
- Rules (in priority order): AWS access keys, private key blocks, bearer tokens, passwords in URLs/config, AWS account IDs, ARNs, S3 bucket URIs, private IPs (RFC 1918), public IPs, IPv6, email addresses, FQDNs, absolute paths, US phone numbers
19+
- Shannon entropy sweep (threshold 4.0 bits/char, min 20 chars) catches high-entropy secrets rules miss
20+
- Configurable denylist: empty by default; populate via `AIRLOCK_BUCKET_DENYLIST` env var (comma-separated) or `add_to_denylist(["name", ...])`; matches bare names AND subdomains
21+
22+
**Story schema and store (`airlock/schema.py`, `airlock/store.py`)**
23+
- War story schema with required AgentErrorTaxonomy tag (`memory | reflection | planning | action | system`)
24+
- `validate_story()`, `scrub_story()` (scrubs all fields, attaches gate snapshot to trust)
25+
- SQLite-backed `StoryStore` with full-text search across all narrative fields
26+
- `_flatten()` helper handles `str | list[str] | None` field types correctly
27+
28+
**MCP server (`airlock/server.py`)**
29+
- Spec-correct JSON-RPC 2.0 over stdio (no SDK dependency)
30+
- Tools: `submit_story` (gate-on-ingest), `search_stories` (scrub-on-egress), `get_story` (scrub-on-egress)
31+
- Double-gating: reject dirty on ingest + scrub every egress path
32+
- `airlock-mcp` console script entry point
33+
- Error codes: -32700 parse, -32601 method-not-found, -32602 invalid-params, -32000 server error
34+
35+
**Seed corpus (`airlock/seed_stories.py`)**
36+
- 3 verified war stories: "Scoped deploy key surprises", "Inventory before deletion", "The quiet identifiers are louder than the key"
37+
- All pass `gate()` clean on every load
38+
39+
**Test suite**
40+
- 69 tests: 41 scrubber unit tests, 18 MCP handler tests, 10 end-to-end subprocess smoke tests
41+
- All fixtures use RFC-reserved / AWS-documented identifiers only
42+
- `conftest.py` autouse fixture ensures hermetic isolation (denylist + store reset per test)
43+
44+
**Packaging and repo**
45+
- `pyproject.toml` (PEP 621, distribution name `agentpier-airlock`)
46+
- GitHub Actions CI matrix: Python 3.10, 3.11, 3.12
47+
- `examples/demo.py` — runnable before/after demo
48+
- Apache 2.0 license

README.md

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Airlock
22

3+
[![CI](https://github.com/gatewaybuddy/agentpier/actions/workflows/ci.yml/badge.svg)](https://github.com/gatewaybuddy/agentpier/actions/workflows/ci.yml)
4+
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
5+
[![Python](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12-blue.svg)](pyproject.toml)
6+
[![PyPI](https://img.shields.io/pypi/v/agentpier-airlock.svg)](https://pypi.org/project/agentpier-airlock/)
7+
38
**Redaction engine + MCP server for AI agent traces. By AgentPier.**
49

510
Airlock is the missing seam between your secret scanner and your PII redactor.
@@ -13,6 +18,96 @@ published without the infra details that would make them a reconnaissance target
1318

1419
---
1520

21+
## Quickstart
22+
23+
```bash
24+
pip install agentpier-airlock
25+
python -c "from airlock.scrubber import scrub; print(scrub('account 123456789012 at 10.0.0.5'))"
26+
# → account <ACCOUNT_ID> at <PRIVATE_IP>
27+
```
28+
29+
Or run the demo:
30+
31+
```bash
32+
git clone https://github.com/gatewaybuddy/agentpier.git
33+
cd agentpier
34+
python examples/demo.py
35+
```
36+
37+
---
38+
39+
## Demo output
40+
41+
```
42+
========================================================================
43+
AIRLOCK — Redaction Demo
44+
========================================================================
45+
46+
--- BEFORE (raw agent trace) -------------------------------------------
47+
[2026-06-24T14:32:01Z] AgentRun#7f3a2c1e — inventory task started
48+
Caller identity: arn:aws:iam::123456789012:user/deploy-agent
49+
Account: 123456789012 Region: us-east-1
50+
51+
Probing EC2 in us-east-1...
52+
→ Instance i-0abc1234def56789 at 203.0.113.42 (public), 10.0.1.55 (private)
53+
→ Security group sg-0123456789abcdef0 allows 0.0.0.0/0:443
54+
55+
S3 buckets found:
56+
s3://example-prod-data/logs/2026-06/
57+
s3://example-backups/snapshots/
58+
Cross-account replication target: --bucket-name example-dr-replica
59+
60+
Secrets Manager: arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db-creds-xK8mP2
61+
Retrieved value: {"password": "s3cr3tP@ssw0rd!", "host": "db.internal.example.net"}
62+
63+
Lambda env vars on arn:aws:lambda:us-east-1:123456789012:function:data-processor:
64+
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
65+
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
66+
67+
Notification endpoint: ops-alerts@example.com
68+
On-call SMS: 407-555-0192
69+
Internal dashboard: https://monitoring.internal.example.net/dashboard
70+
71+
[2026-06-24T14:32:04Z] Inventory complete — 2 instances, 3 buckets, 1 function
72+
73+
--- AFTER (scrubbed) ---------------------------------------------------
74+
[2026-06-24T14:32:01Z] AgentRun#7f3a2c1e — inventory task started
75+
Caller identity: <ARN>
76+
Account: <ACCOUNT_ID> Region: us-east-1
77+
78+
Probing EC2 in us-east-1...
79+
→ Instance i-0abc1234def56789 at <PUBLIC_IP> (public), <PRIVATE_IP> (private)
80+
→ Security group sg-0123456789abcdef0 allows <PUBLIC_IP>/0:443
81+
82+
S3 buckets found:
83+
<BUCKET><PATH>
84+
<BUCKET><PATH>
85+
Cross-account replication target: --<BUCKET>
86+
87+
Secrets Manager: <ARN>
88+
Retrieved value: {"password": "s3cr3tP@ssw0rd!", "host": "<FQDN>"}
89+
90+
Lambda env vars on <ARN>
91+
AWS_ACCESS_KEY_ID=<ACCESS_KEY>
92+
AWS_SECRET_ACCESS_KEY=<SECRET>
93+
94+
Notification endpoint: <EMAIL>
95+
On-call SMS: <PHONE>
96+
Internal dashboard: https://<FQDN>/dashboard
97+
98+
[2026-06-24T14:32:04Z] Inventory complete — 2 instances, 3 buckets, 1 function
99+
100+
--- GATE RESULT --------------------------------------------------------
101+
clean: False status: DIRTY — quarantined
102+
findings: 22
103+
104+
========================================================================
105+
22 sensitive tokens redacted. The lesson survives; the identifiers don't.
106+
========================================================================
107+
```
108+
109+
---
110+
16111
## What it catches
17112

18113
| Category | Examples | Placeholder |
@@ -34,13 +129,7 @@ published without the infra details that would make them a reconnaissance target
34129

35130
---
36131

37-
## Quick start
38-
39-
```bash
40-
git clone https://github.com/agentpier/airlock.git
41-
cd airlock
42-
python3 -m pytest airlock/tests/ -v
43-
```
132+
## Usage
44133

45134
### Scrub a string
46135

@@ -70,13 +159,17 @@ Or via environment variable (for MCP server deployments):
70159

71160
```bash
72161
export AIRLOCK_BUCKET_DENYLIST="my-prod-bucket,internal.corp.net,staging-data"
73-
python3 -m airlock.server
162+
airlock-mcp
74163
```
75164

76165
### Start the MCP server
77166

78167
```bash
79-
PYTHONPATH=/path/to/parent-of-airlock python3 airlock/server.py
168+
# As a console script (after pip install):
169+
AIRLOCK_BUCKET_DENYLIST="my-bucket" airlock-mcp
170+
171+
# Or directly from the repo:
172+
PYTHONPATH=. python airlock/server.py
80173
```
81174

82175
See `airlock/SKILL.md` for Claude Code / Cursor install config.
@@ -95,7 +188,7 @@ completely or it is quarantined.
95188

96189
**Denylist is empty by default**: Airlock ships with zero hardcoded infra names.
97190
You bring your own via `AIRLOCK_BUCKET_DENYLIST` or `add_to_denylist()`. This
98-
keeps the OSS release from embedding any org's topology.
191+
keeps the library from embedding any org's topology.
99192

100193
---
101194

@@ -125,7 +218,8 @@ See `airlock/STORY_TEMPLATE.md` for the fill-in-the-blanks template.
125218
## Running tests
126219

127220
```bash
128-
PYTHONPATH=/path/to/parent-of-airlock python3 -m pytest airlock/tests/ -v
221+
# From the repo root
222+
PYTHONPATH=. python -m pytest airlock/tests/ -v
129223
```
130224

131225
The test suite includes unit tests for the scrubber (41 tests), handler tests

airlock/py.typed

Whitespace-only changes.

airlock/server.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,5 +394,10 @@ def _write(obj: dict) -> None:
394394
sys.stdout.flush()
395395

396396

397-
if __name__ == "__main__":
397+
def main() -> None:
398+
"""Entry point for the `airlock-mcp` console script."""
398399
run_stdio()
400+
401+
402+
if __name__ == "__main__":
403+
main()

0 commit comments

Comments
 (0)