Metadata-Version: 2.2
Name: egressai
Version: 0.1.6
Summary: Local-first redaction engine for AI egress security
Author-email: EgressAI <security@egressai.dev>
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Topic :: Security
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: egressai-detect-secrets>=1.5.1
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"

# EgressAI

EgressAI is a local-first redaction engine for AI egress security.

The first package takes text, detects secrets, replaces them with stable placeholders, and returns
structured findings without exposing plaintext secret values in the public result.

## Install

```bash
pip install egressai
```

The package depends on `egressai-detect-secrets` for provider-aware secret detection.

## Python API

Redact text:

```python
from egressai import RedactionEngine

secret = "sk-proj-" + "a" * 40
engine = RedactionEngine()
result = engine.redact(f"OPENAI_API_KEY={secret}")

print(result.redacted_text)
```

Output:

```text
OPENAI_API_KEY={{EGRESSAI_SECRET_OPENAI_001}}
```

Inspect structured findings:

```python
for finding in result.findings:
    print(finding.type, finding.line, finding.column, finding.placeholder)
```

Output:

```text
secret.openai_api_key 1 16 {{EGRESSAI_SECRET_OPENAI_001}}
```

Restore redacted text during the same engine session:

```python
restored = engine.restore(result.redacted_text)
```

The restore map is memory-only and is not included in structured output.

Structured result:

```python
payload = result.to_dict()
```

The structured output includes:

- `redacted_text`
- `findings`
- `stats`

Each finding includes zero-based character offsets (`start`, `end`) plus
one-based `line` and `column` metadata for locating the sensitive value in files,
logs, and pasted diffs.

Plaintext secret values are not included in `result.to_dict()`.

Redact a larger pasted context:

```python
text = """
OPENAI_API_KEY=sk-proj-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
GITHUB_TOKEN=ghp_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
DATABASE_URL=postgres://admin:password@prod.internal:5432/app
"""

result = RedactionEngine().redact(text)
print(result.redacted_text)
```

Output:

```text
OPENAI_API_KEY={{EGRESSAI_SECRET_OPENAI_001}}
GITHUB_TOKEN={{EGRESSAI_SECRET_GITHUB_001}}
DATABASE_URL={{EGRESSAI_SECRET_DB_URL_001}}
```

## CLI

Redact inline text:

```bash
egressai redact "OPENAI_API_KEY=sk-proj-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
```

Output:

```text
OPENAI_API_KEY={{EGRESSAI_SECRET_OPENAI_001}}
```

Redact a file:

```bash
egressai redact ./prompt.txt
```

Redact stdin:

```bash
cat ./logs.txt | egressai redact
```

Existing file paths are read from disk. Any other input is treated as literal text,
including large multi-line contexts passed as one argument. For very large prompts
or logs, stdin is usually the most reliable shell interface.

Return the full redaction result as JSON:

```bash
egressai redact --json ./sample.env
```

Scan without printing redacted text:

```bash
egressai scan ./sample.env
```

Return findings and stats as JSON:

```bash
egressai scan --json ./sample.env
```

Example JSON shape:

```json
{
  "findings": [
    {
      "category": "secret",
      "column": 16,
      "confidence": 0.99,
      "detector": "egressai.openai_key",
      "end": 63,
      "id": "finding_000001",
      "line": 1,
      "placeholder": "{{EGRESSAI_SECRET_OPENAI_001}}",
      "severity": "critical",
      "start": 15,
      "type": "secret.openai_api_key"
    }
  ],
  "stats": {
    "findings_count": 1,
    "pii_count": 0,
    "secrets_count": 1
  }
}
```

Return one finding per JSON Lines record:

```bash
egressai scan --jsonl ./sample.env
```

Example JSONL record:

```json
{"category":"secret","column":16,"confidence":0.99,"detector":"egressai.openai_key","end":63,"id":"finding_000001","line":1,"placeholder":"{{EGRESSAI_SECRET_OPENAI_001}}","severity":"critical","start":15,"type":"secret.openai_api_key"}
```

Fail when findings are present:

```bash
egressai scan --fail-on-findings ./sample.env
```

Use a custom placeholder prefix:

```bash
egressai redact --placeholder-prefix SAFE ./prompt.txt
```

## Codex Integration

The repo includes a hook-only Codex plugin at `plugins/egressai`.

The plugin does not include skills. It uses local hooks as the enforcement
boundary, but the MVP behavior is allow-and-redact rather than block:

- `UserPromptSubmit` redacts sensitive values from user prompts and allows the request.
- `PreToolUse` restores known placeholders locally, then reports only redacted hook output.
- `PostToolUse` redacts sensitive values from tool outputs before they re-enter context.

Hook placeholder mappings are stored in a local session vault so separate hook
invocations can reuse and restore the same placeholders. The vault path defaults
to the system temp directory and can be overridden with
`EGRESSAI_CODEX_VAULT_FILE`.

Codex hooks are lifecycle commands. Current public Codex docs describe blocking
and feedback behavior, not a guaranteed payload-rewrite contract, so EgressAI
emits the sanitized payload in hook feedback and keeps the restore mapping local.

Install the local package before using the hooks:

```bash
python3 -m pip install --upgrade "egressai>=0.1.6"
```

Then add this repository marketplace to Codex, restart Codex, open `/plugins`,
and install EgressAI:

```bash
codex plugin marketplace add .
```

After installing, open `/hooks` to review and trust the EgressAI hook
definitions. A finding should produce a successful hook result with
`"decision":"allow"` and a payload containing EgressAI placeholders, not the
plaintext secret.

## Larger Context Example

The fixture at `tests/fixtures/messy_context.txt` contains a pasted support/debug
report with a code diff, request headers, curl command, JSON payload, env vars,
repeated provider keys, JWTs, database URLs, and an Azure connection string.

Run it through the CLI:

```bash
egressai redact tests/fixtures/messy_context.txt
```

The redacted output preserves the surrounding debugging context while replacing
secrets with provider-aware placeholders such as:

```text
OPENAI_API_KEY="{{EGRESSAI_SECRET_OPENAI_001}}"
GITHUB_TOKEN="{{EGRESSAI_SECRET_GITHUB_001}}"
"Authorization": "Bearer {{EGRESSAI_TOKEN_JWT_001}}"
DATABASE_URL="{{EGRESSAI_SECRET_DB_URL_001}}"
```

## Exit Codes

By default, CLI commands exit `0` even when findings are detected.

With `--fail-on-findings`:

- exits `0` when no findings are detected
- exits `1` when findings are detected
- exits `2` for CLI usage errors

## Supported Secret Types

Initial support includes:

- OpenAI API keys
- Anthropic API keys
- Google/Gemini API keys
- GitHub tokens
- GitLab tokens
- AWS access keys
- Stripe keys
- Slack tokens
- npm tokens, including `.npmrc` auth tokens and `NPM_TOKEN=npm_...` env values
- PyPI tokens
- Hugging Face tokens
- JWTs, including JWT-like tokens with non-base64url signature suffixes
- private key blocks
- database URLs, including JSON-escaped URL values such as `postgres:\/\/...`
- Azure connection strings
- generic `.env`-style secret assignments

Provider mappings are covered by tests so each supported detector returns the
expected EgressAI type, detector name, and placeholder subtype.

The test suite includes a messy real-world fixture with a pasted user report,
code diff, curl commands, runtime env vars, JSON payloads, repeated secrets, and
multiple provider token types.

## Release Notes

### 0.1.5

- Added hook-only Codex plugin scaffolding.
- Added local `egressai.codex_hook` hook runner and `egressai-codex-hook`
  console script.

### 0.1.4

- Expanded README examples and usage documentation.

### 0.1.3

- Added finding `line` and `column` metadata.
- Added same-session restore support through `RedactionEngine.restore()`.
- Added a messy real-world fixture corpus.
- Added JSON-escaped database URL redaction.
- Added `egressai scan --jsonl`.

## Maintainer Notes

Every user-visible change should update this README in the same change set.

Before publishing:

```bash
python3 -m pytest
python3 -m build
python3 -m twine check dist/egressai-*
```

Publish the current release artifacts:

```bash
LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 PYTHONIOENCODING=utf-8 python3 -m twine upload dist/egressai-0.1.5*
```
