@aictrl/hush 0.1.0 → 0.1.6
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.
- package/.github/workflows/ci.yml +46 -0
- package/.github/workflows/e2e-opencode.yml +126 -0
- package/.github/workflows/opencode-review.yml +101 -0
- package/.github/workflows/publish.yml +44 -0
- package/CLAUDE.md +6 -0
- package/CONTRIBUTING.md +29 -0
- package/Dockerfile +25 -0
- package/GEMINI.md +6 -0
- package/README.md +86 -71
- package/dist/cli.js +11 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.js +54 -36
- package/dist/index.js.map +1 -1
- package/dist/middleware/redactor.d.ts.map +1 -1
- package/dist/middleware/redactor.js +12 -7
- package/dist/middleware/redactor.js.map +1 -1
- package/dist/vault/token-vault.d.ts.map +1 -1
- package/dist/vault/token-vault.js +103 -16
- package/dist/vault/token-vault.js.map +1 -1
- package/install.sh +37 -0
- package/logo.svg +31 -0
- package/package.json +5 -4
- package/scripts/e2e-gateway-harness.ts +62 -0
- package/scripts/e2e-mock-upstream.mjs +55 -0
- package/scripts/e2e-opencode.sh +217 -0
- package/src/cli.ts +20 -0
- package/src/index.ts +261 -0
- package/src/lib/dashboard.ts +180 -0
- package/src/lib/logger.ts +72 -0
- package/src/middleware/redactor.ts +155 -0
- package/src/vault/token-vault.ts +249 -0
- package/tests/proxy.test.ts +258 -0
- package/tests/redaction.test.ts +102 -0
- package/tests/universal-proxy.test.ts +160 -0
- package/tests/vault.test.ts +73 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, master ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main, master ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Build & Test
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
pull-requests: write
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
strategy:
|
|
19
|
+
matrix:
|
|
20
|
+
node-version: [20.x, 22.x]
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
26
|
+
uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: ${{ matrix.node-version }}
|
|
29
|
+
cache: 'npm'
|
|
30
|
+
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: npm ci
|
|
33
|
+
|
|
34
|
+
- name: Build
|
|
35
|
+
run: npm run build
|
|
36
|
+
|
|
37
|
+
- name: Run Tests with Coverage
|
|
38
|
+
run: npm test
|
|
39
|
+
|
|
40
|
+
# Optional: Post coverage report to PR
|
|
41
|
+
- name: Report Coverage
|
|
42
|
+
if: github.event_name == 'pull_request' && matrix.node-version == '22.x'
|
|
43
|
+
uses: davelosert/vitest-coverage-report-action@v2
|
|
44
|
+
with:
|
|
45
|
+
json-summary-path: ./coverage/coverage-summary.json
|
|
46
|
+
json-final-path: ./coverage/coverage-final.json
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
name: E2E - OpenCode + GLM-5 via Hush Gateway
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
use_real_api:
|
|
9
|
+
description: 'Use real ZhipuAI API key (requires ZHIPUAI_API_KEY secret)'
|
|
10
|
+
type: boolean
|
|
11
|
+
default: false
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: e2e-opencode-${{ github.event.pull_request.number || github.ref }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
e2e-gateway-interception:
|
|
22
|
+
name: Gateway PII Interception (Mock Upstream)
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Use Node.js 22.x
|
|
29
|
+
uses: actions/setup-node@v4
|
|
30
|
+
with:
|
|
31
|
+
node-version: 22.x
|
|
32
|
+
cache: 'npm'
|
|
33
|
+
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: npm ci
|
|
36
|
+
|
|
37
|
+
- name: Run E2E interception test
|
|
38
|
+
run: |
|
|
39
|
+
chmod +x scripts/e2e-opencode.sh
|
|
40
|
+
./scripts/e2e-opencode.sh
|
|
41
|
+
env:
|
|
42
|
+
CI: true
|
|
43
|
+
|
|
44
|
+
e2e-opencode-live:
|
|
45
|
+
name: OpenCode + GLM-5 Live E2E
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
if: >
|
|
48
|
+
github.event_name == 'workflow_dispatch' &&
|
|
49
|
+
github.event.inputs.use_real_api == 'true'
|
|
50
|
+
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v4
|
|
53
|
+
|
|
54
|
+
- name: Use Node.js 22.x
|
|
55
|
+
uses: actions/setup-node@v4
|
|
56
|
+
with:
|
|
57
|
+
node-version: 22.x
|
|
58
|
+
cache: 'npm'
|
|
59
|
+
|
|
60
|
+
- name: Install dependencies & build
|
|
61
|
+
run: npm ci && npm run build
|
|
62
|
+
|
|
63
|
+
- name: Start Hush Gateway (background)
|
|
64
|
+
run: |
|
|
65
|
+
DEBUG=true node dist/cli.js > /tmp/gateway.log 2>&1 &
|
|
66
|
+
GATEWAY_PID=$!
|
|
67
|
+
echo "GATEWAY_PID=$GATEWAY_PID" >> $GITHUB_ENV
|
|
68
|
+
|
|
69
|
+
# Wait for gateway to be ready
|
|
70
|
+
for i in {1..15}; do
|
|
71
|
+
if curl -sf http://127.0.0.1:4000/health > /dev/null 2>&1; then
|
|
72
|
+
echo "Gateway is ready"
|
|
73
|
+
break
|
|
74
|
+
fi
|
|
75
|
+
echo "Waiting for gateway... ($i/15)"
|
|
76
|
+
sleep 1
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
- name: Check vault is empty before test
|
|
80
|
+
run: |
|
|
81
|
+
HEALTH_BEFORE=$(curl -sf http://127.0.0.1:4000/health)
|
|
82
|
+
echo "Health before: $HEALTH_BEFORE"
|
|
83
|
+
VAULT_BEFORE=$(echo "$HEALTH_BEFORE" | jq -r '.vaultSize // 0')
|
|
84
|
+
echo "Vault size before: $VAULT_BEFORE"
|
|
85
|
+
|
|
86
|
+
- name: Send PII-laden request through gateway to real GLM-5
|
|
87
|
+
env:
|
|
88
|
+
ZHIPU_API_KEY: ${{ secrets.ZHIPUAI_API_KEY }}
|
|
89
|
+
timeout-minutes: 2
|
|
90
|
+
run: |
|
|
91
|
+
# Send a real GLM-5 chat completion through the Hush gateway
|
|
92
|
+
# This proves PII interception works with the actual ZhipuAI API
|
|
93
|
+
HTTP_CODE=$(curl -s -o /tmp/response.json -w "%{http_code}" --max-time 60 \
|
|
94
|
+
-X POST "http://127.0.0.1:4000/api/paas/v4/chat/completions" \
|
|
95
|
+
-H "Content-Type: application/json" \
|
|
96
|
+
-H "Authorization: Bearer $ZHIPU_API_KEY" \
|
|
97
|
+
-d '{
|
|
98
|
+
"model": "glm-5",
|
|
99
|
+
"messages": [{"role": "user", "content": "Please confirm receipt. My email is testuser@example-corp.com and server IP is 10.42.99.7. Credentials: api_key=secret_test_a1b2c3d4e5f6g7h8i9j0"}]
|
|
100
|
+
}') || true
|
|
101
|
+
echo "HTTP Status: $HTTP_CODE"
|
|
102
|
+
echo "Response: $(cat /tmp/response.json | head -c 500)"
|
|
103
|
+
echo ""
|
|
104
|
+
echo "--- Gateway logs ---"
|
|
105
|
+
cat /tmp/gateway.log 2>/dev/null || true
|
|
106
|
+
|
|
107
|
+
- name: Verify PII was intercepted
|
|
108
|
+
run: |
|
|
109
|
+
HEALTH_AFTER=$(curl -sf http://127.0.0.1:4000/health)
|
|
110
|
+
echo "Health after: $HEALTH_AFTER"
|
|
111
|
+
VAULT_AFTER=$(echo "$HEALTH_AFTER" | jq -r '.vaultSize // 0')
|
|
112
|
+
echo "Vault size after: $VAULT_AFTER"
|
|
113
|
+
|
|
114
|
+
if [ "$VAULT_AFTER" -gt 0 ]; then
|
|
115
|
+
echo "SUCCESS: Gateway vault contains $VAULT_AFTER token(s) - PII was intercepted!"
|
|
116
|
+
else
|
|
117
|
+
echo "FAILURE: Gateway vault is empty - PII may not have been intercepted"
|
|
118
|
+
exit 1
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
- name: Cleanup
|
|
122
|
+
if: always()
|
|
123
|
+
run: |
|
|
124
|
+
if [ -n "$GATEWAY_PID" ]; then
|
|
125
|
+
kill $GATEWAY_PID 2>/dev/null || true
|
|
126
|
+
fi
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
name: OpenCode Review
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
branches: [main, master]
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: opencode-${{ github.event.pull_request.number || github.ref }}
|
|
9
|
+
cancel-in-progress: true
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
opencode:
|
|
13
|
+
name: OpenCode AI Review
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
pull-requests: write
|
|
17
|
+
issues: write
|
|
18
|
+
contents: read
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout repository
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
|
|
25
|
+
- name: Check for Relevant Changes
|
|
26
|
+
id: check_changes
|
|
27
|
+
env:
|
|
28
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
29
|
+
run: |
|
|
30
|
+
SHA=${{ github.event.pull_request.head.sha || github.sha }}
|
|
31
|
+
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
32
|
+
|
|
33
|
+
if [ -z "$PR_NUMBER" ]; then
|
|
34
|
+
echo "Not a pull request, proceeding."
|
|
35
|
+
echo "skip=false" >> $GITHUB_OUTPUT
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# 1. Check if this SHA was already reviewed
|
|
40
|
+
echo "Checking if SHA $SHA was already reviewed..."
|
|
41
|
+
REVIEW_COMMENTS=$(gh pr view $PR_NUMBER --json comments --jq '.comments[].body' | grep -c "Reviewed SHA:" || true)
|
|
42
|
+
LAST_REVIEW_SHA=$(gh pr view $PR_NUMBER --json comments --jq '.comments[].body' | grep -o "Reviewed SHA: [a-f0-9]\{40\}" | tail -n 1 | cut -d' ' -f3)
|
|
43
|
+
|
|
44
|
+
if [ "$LAST_REVIEW_SHA" == "$SHA" ]; then
|
|
45
|
+
echo "This commit ($SHA) has already been reviewed. Skipping."
|
|
46
|
+
echo "skip=true" >> $GITHUB_OUTPUT
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 2. Skip if 2+ reviews already exist (unless manually triggered)
|
|
51
|
+
IS_MANUAL="${{ github.event_name == 'workflow_dispatch' }}"
|
|
52
|
+
if [ "$REVIEW_COMMENTS" -ge 2 ] && [ "$IS_MANUAL" != "true" ]; then
|
|
53
|
+
echo "PR already has $REVIEW_COMMENTS AI reviews. Skipping (use workflow_dispatch to force)."
|
|
54
|
+
echo "skip=true" >> $GITHUB_OUTPUT
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# 3. Check if there are any "actual code" changes
|
|
59
|
+
echo "Checking changed files..."
|
|
60
|
+
# We check src/ and tests/ for relevant logic changes
|
|
61
|
+
CODE_CHANGES=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }}...$SHA | grep -E '\.(ts|js|json|sh|yml|yaml)$' | grep -vE '^docs/|.*\.md$' || true)
|
|
62
|
+
|
|
63
|
+
if [ -z "$CODE_CHANGES" ]; then
|
|
64
|
+
echo "No actual code changes detected. Skipping review."
|
|
65
|
+
echo "skip=true" >> $GITHUB_OUTPUT
|
|
66
|
+
else
|
|
67
|
+
echo "Actual code changes detected:"
|
|
68
|
+
echo "$CODE_CHANGES"
|
|
69
|
+
echo "skip=false" >> $GITHUB_OUTPUT
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
- name: Setup OpenCode
|
|
73
|
+
if: steps.check_changes.outputs.skip != 'true'
|
|
74
|
+
env:
|
|
75
|
+
ZHIPU_API_KEY: ${{ secrets.ZHIPUAI_API_KEY }}
|
|
76
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
77
|
+
run: |
|
|
78
|
+
# Use GITHUB_TOKEN to avoid rate limits when fetching version info
|
|
79
|
+
curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path
|
|
80
|
+
echo "$HOME/.opencode/bin" >> $GITHUB_PATH
|
|
81
|
+
|
|
82
|
+
- name: Direct OpenCode Review
|
|
83
|
+
if: steps.check_changes.outputs.skip != 'true'
|
|
84
|
+
env:
|
|
85
|
+
ZHIPU_API_KEY: ${{ secrets.ZHIPUAI_API_KEY }}
|
|
86
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
87
|
+
run: |
|
|
88
|
+
SHA=${{ github.event.pull_request.head.sha || github.sha }}
|
|
89
|
+
echo "Starting review with GLM-5 for SHA $SHA..."
|
|
90
|
+
|
|
91
|
+
$HOME/.opencode/bin/opencode run --model zai-coding-plan/glm-5 "Review the changes in this PR for the Hush Semantic Gateway.
|
|
92
|
+
|
|
93
|
+
Focus areas:
|
|
94
|
+
1. **Redaction Logic**: Ensure PII patterns are robust and handle edge cases in tool outputs (like JSON or CLI tables).
|
|
95
|
+
2. **Streaming Integrity**: Check that the SSE/streaming proxy logic doesn't buffer unnecessarily or break the rehydration flow.
|
|
96
|
+
3. **Security**: Look for potential PII leaks or insecure token handling in the vault.
|
|
97
|
+
4. **Reliability**: Ensure the proxy handles upstream errors gracefully.
|
|
98
|
+
|
|
99
|
+
Keep the summary concise but technical. Post findings as a markdown comment on the PR.
|
|
100
|
+
|
|
101
|
+
**CRITICAL**: Include the string 'Reviewed SHA: $SHA' at the very end of your comment so I can track which commits have been reviewed."
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
name: Build, Test & Publish
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
environment: release
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: 24
|
|
24
|
+
cache: 'npm'
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: npm ci
|
|
28
|
+
|
|
29
|
+
- name: Build
|
|
30
|
+
run: npm run build
|
|
31
|
+
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: npm test
|
|
34
|
+
|
|
35
|
+
- name: Verify version matches tag
|
|
36
|
+
run: |
|
|
37
|
+
PKG_VERSION="v$(node -p "require('./package.json').version")"
|
|
38
|
+
if [ "$PKG_VERSION" != "${{ github.ref_name }}" ]; then
|
|
39
|
+
echo "::error::Tag ${{ github.ref_name }} does not match package.json version $PKG_VERSION"
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
- name: Publish to npm
|
|
44
|
+
run: npm publish --provenance --access public
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# hush 🛡️ Mandates
|
|
2
|
+
|
|
3
|
+
## Development Workflow
|
|
4
|
+
- **PR-Only Pushes:** All code changes, documentation updates, and asset additions MUST be submitted via a Pull Request. Direct pushes to the `master` branch are strictly prohibited.
|
|
5
|
+
- **Verification:** Every PR must pass all existing tests (`npm test`) and maintain or improve the current test coverage before merging.
|
|
6
|
+
- **Security First:** Never bypass security protocols (like `HUSH_AUTH_TOKEN` or local binding) during development or testing.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Contributing to AICtrl Gateway
|
|
2
|
+
|
|
3
|
+
We're excited that you're interested in contributing to AICtrl Gateway! Here's a quick guide to help you get started.
|
|
4
|
+
|
|
5
|
+
## How to Contribute
|
|
6
|
+
|
|
7
|
+
1. **Fork the repository** on GitHub.
|
|
8
|
+
2. **Clone your fork** locally: `git clone https://github.com/YOUR_USERNAME/gateway.git`
|
|
9
|
+
3. **Create a new branch** for your feature or bug fix: `git checkout -b my-feature`
|
|
10
|
+
4. **Make your changes** and ensure tests pass: `npm test`
|
|
11
|
+
5. **Commit your changes** with a clear message.
|
|
12
|
+
6. **Push to your branch**: `git push origin my-feature`
|
|
13
|
+
7. **Open a Pull Request** against the `main` branch.
|
|
14
|
+
|
|
15
|
+
## Development Setup
|
|
16
|
+
|
|
17
|
+
1. Install dependencies: `npm install`
|
|
18
|
+
2. Run tests: `npm test`
|
|
19
|
+
3. Build the project: `npm run build`
|
|
20
|
+
|
|
21
|
+
## Code Style
|
|
22
|
+
|
|
23
|
+
Please ensure your code follows the existing style and is well-documented with TSDoc comments.
|
|
24
|
+
|
|
25
|
+
## Reporting Issues
|
|
26
|
+
|
|
27
|
+
If you find a bug or have a feature request, please [open an issue](https://github.com/aictrl/gateway/issues) on GitHub.
|
|
28
|
+
|
|
29
|
+
Thank you for your help!
|
package/Dockerfile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# hush 🛡️ - Docker Gateway
|
|
2
|
+
FROM node:18-slim
|
|
3
|
+
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
# Install dependencies first for layer caching
|
|
7
|
+
COPY package*.json ./
|
|
8
|
+
RUN npm install
|
|
9
|
+
|
|
10
|
+
# Copy source
|
|
11
|
+
COPY . .
|
|
12
|
+
|
|
13
|
+
# Build TypeScript
|
|
14
|
+
RUN npm run build
|
|
15
|
+
|
|
16
|
+
# Default environment
|
|
17
|
+
ENV PORT=4000
|
|
18
|
+
ENV HUSH_HOST=0.0.0.0
|
|
19
|
+
# Note: Bind to 0.0.0.0 inside container so Docker can forward it,
|
|
20
|
+
# but host binding remains safe.
|
|
21
|
+
|
|
22
|
+
EXPOSE 4000
|
|
23
|
+
|
|
24
|
+
# Start the gateway
|
|
25
|
+
CMD ["node", "dist/cli.js"]
|
package/GEMINI.md
ADDED
package/README.md
CHANGED
|
@@ -3,110 +3,125 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
**hush** is a Semantic Security Gateway for AI agents.
|
|
6
|
-
|
|
6
|
+
It sits between your AI tools (Claude Code, Codex, OpenCode, Gemini CLI) and LLM providers, ensuring that sensitive data — emails, IP addresses, API keys, credit cards — never leaves your machine.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## Quick Start
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g @aictrl/hush
|
|
12
|
+
hush
|
|
13
|
+
```
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
Hush starts on `http://127.0.0.1:4000`. Now point your AI tool at it:
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
### Claude Code
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
Add to `~/.claude/settings.json`:
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"env": {
|
|
24
|
+
"ANTHROPIC_BASE_URL": "http://127.0.0.1:4000"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
> **Note:** Claude Code subscription (OAuth) tokens are currently blocked by Anthropic for third-party proxies ([anthropics/claude-code#28091](https://github.com/anthropics/claude-code/issues/28091)). If you hit a 401, add `"ANTHROPIC_AUTH_TOKEN": "sk-ant-..."` to the env block above.
|
|
25
30
|
|
|
26
|
-
###
|
|
31
|
+
### Codex (OpenAI)
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
Add to `~/.codex/config.toml` (or `.codex/config.toml` in your project):
|
|
34
|
+
|
|
35
|
+
```toml
|
|
36
|
+
model_provider = "hush"
|
|
37
|
+
|
|
38
|
+
[model_providers.hush]
|
|
39
|
+
base_url = "http://127.0.0.1:4000/v1"
|
|
30
40
|
```
|
|
31
41
|
|
|
32
|
-
###
|
|
42
|
+
### OpenCode (ZhipuAI GLM-5)
|
|
33
43
|
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
hush --dashboard
|
|
37
|
-
```
|
|
38
|
-
hush will start listening on `http://127.0.0.1:4000`.
|
|
44
|
+
Create `opencode.json` in your project root:
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"provider": {
|
|
49
|
+
"zai-coding-plan": {
|
|
50
|
+
"options": {
|
|
51
|
+
"baseURL": "http://127.0.0.1:4000/api/coding/paas/v4"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
41
57
|
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
export ANTHROPIC_BASE_URL=http://127.0.0.1:4000
|
|
45
|
-
claude
|
|
46
|
-
```
|
|
58
|
+
### Gemini CLI
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
export OPENAI_BASE_URL=http://127.0.0.1:4000/v1
|
|
51
|
-
```
|
|
60
|
+
Gemini CLI only supports env vars for endpoint override (no settings file option):
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
export GOOGLE_GENERATIVE_AI_BASE_URL=http://127.0.0.1:4000
|
|
62
|
+
```bash
|
|
63
|
+
CODE_ASSIST_ENDPOINT=http://127.0.0.1:4000 gemini
|
|
64
|
+
```
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
export CODE_ASSIST_ENDPOINT=http://127.0.0.1:4000
|
|
60
|
-
```
|
|
66
|
+
### Verify it works
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
When your AI tool sends a request containing PII, the hush terminal shows:
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
| `HUSH_DASHBOARD` | Set to `true` to enable the TUI dashboard. | `false` |
|
|
70
|
+
```
|
|
71
|
+
INFO: Redacted sensitive data from request path="/v1/messages" tokenCount=2 duration=1
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Your tool still sees the real data (rehydrated locally). The LLM provider only ever sees tokens like `[USER_EMAIL_f22c5a]`.
|
|
70
75
|
|
|
71
76
|
## How it Works
|
|
72
77
|
|
|
73
|
-
1.
|
|
74
|
-
2.
|
|
75
|
-
3.
|
|
76
|
-
4.
|
|
77
|
-
5.
|
|
78
|
+
1. **Intercept** — Hush sits on your machine between your AI tool and the LLM provider.
|
|
79
|
+
2. **Redact** — Before forwarding, it scans for PII and swaps it for deterministic tokens (`bulat@aictrl.dev` → `[USER_EMAIL_f22c5a]`).
|
|
80
|
+
3. **Vault** — Original values are saved in a local, in-memory TokenVault (auto-expires after 1 hour).
|
|
81
|
+
4. **Forward** — The redacted request goes to the provider. They never see your real data.
|
|
82
|
+
5. **Rehydrate** — Responses come back with tokens replaced by originals before reaching your tool.
|
|
78
83
|
|
|
79
|
-
##
|
|
84
|
+
## Supported Tools
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
| Tool | Config | Route |
|
|
87
|
+
|------|--------|-------|
|
|
88
|
+
| Claude Code | `~/.claude/settings.json` | `/v1/messages` → Anthropic |
|
|
89
|
+
| Codex | `~/.codex/config.toml` | `/v1/chat/completions` → OpenAI |
|
|
90
|
+
| OpenCode | `opencode.json` | `/api/paas/v4/**` → ZhipuAI |
|
|
91
|
+
| Gemini CLI | `CODE_ASSIST_ENDPOINT` env var | `/v1beta/models/**` → Google |
|
|
92
|
+
| Any tool | Point base URL at hush | `/*` catch-all with auto-detect |
|
|
82
93
|
|
|
83
|
-
|
|
84
|
-
- npm
|
|
94
|
+
Hush forwards your existing auth headers transparently — no API keys need to be reconfigured.
|
|
85
95
|
|
|
86
|
-
|
|
96
|
+
## Features
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
- **Semantic Redaction** — Identifies emails, IPs, secrets, credit cards, phone numbers. Deterministic hash-based tokens (same input → same token).
|
|
99
|
+
- **Local Rehydration** — Restores original values in responses locally. You see real data; the provider sees tokens.
|
|
100
|
+
- **Streaming Support** — SSE-aware rehydration handles tokens split across network chunks.
|
|
101
|
+
- **Live Dashboard** — `hush --dashboard` for a real-time TUI showing PII being blocked.
|
|
102
|
+
- **Zero-Trust** — PII never leaves your machine. Binds to `127.0.0.1` by default.
|
|
103
|
+
- **Universal Proxy** — One instance handles all providers simultaneously. Auto-detects from request path.
|
|
93
104
|
|
|
94
|
-
|
|
105
|
+
## Configuration
|
|
95
106
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
| Variable | Description | Default |
|
|
108
|
+
|----------|-------------|---------|
|
|
109
|
+
| `PORT` | Gateway listen port | `4000` |
|
|
110
|
+
| `HUSH_HOST` | Bind address | `127.0.0.1` |
|
|
111
|
+
| `HUSH_AUTH_TOKEN` | Require auth on all requests to the gateway itself | — |
|
|
112
|
+
| `HUSH_DASHBOARD` | Enable TUI dashboard | `false` |
|
|
113
|
+
| `DEBUG` | Show vault size in `/health` | `false` |
|
|
99
114
|
|
|
100
|
-
|
|
115
|
+
## Development
|
|
101
116
|
|
|
102
117
|
```bash
|
|
103
|
-
|
|
118
|
+
git clone https://github.com/aictrl-dev/hush.git
|
|
119
|
+
cd hush && npm install
|
|
120
|
+
npm run dev # dev mode with tsx
|
|
121
|
+
npm test # run tests
|
|
122
|
+
npm run build # production build
|
|
104
123
|
```
|
|
105
124
|
|
|
106
|
-
## Contributing
|
|
107
|
-
|
|
108
|
-
We welcome contributions! Please see our [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
109
|
-
|
|
110
125
|
## License
|
|
111
126
|
|
|
112
|
-
|
|
127
|
+
Apache License 2.0 — see [LICENSE](LICENSE).
|
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,17 @@ import { app } from './index.js';
|
|
|
3
3
|
import { createLogger } from './lib/logger.js';
|
|
4
4
|
const log = createLogger('hush-cli');
|
|
5
5
|
const PORT = process.env.PORT || 4000;
|
|
6
|
-
app.listen(PORT, () => {
|
|
6
|
+
const server = app.listen(PORT, () => {
|
|
7
7
|
log.info(`Hush Semantic Gateway is listening on http://localhost:${PORT}`);
|
|
8
|
-
log.info(`
|
|
8
|
+
log.info(`Routes: /v1/messages → Anthropic, /v1/chat/completions → OpenAI, /api/paas/v4/** → ZhipuAI, * → Google`);
|
|
9
|
+
});
|
|
10
|
+
server.on('error', (err) => {
|
|
11
|
+
if (err.code === 'EADDRINUSE') {
|
|
12
|
+
log.error(`Port ${PORT} is already in use. Stop the other process or use PORT=<number> hush`);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
log.error({ err }, 'Failed to start server');
|
|
16
|
+
}
|
|
17
|
+
process.exit(1);
|
|
9
18
|
});
|
|
10
19
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;AACrC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AAEtC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;AACrC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AAEtC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACnC,GAAG,CAAC,IAAI,CAAC,0DAA0D,IAAI,EAAE,CAAC,CAAC;IAC3E,GAAG,CAAC,IAAI,CAAC,wGAAwG,CAAC,CAAC;AACrH,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;IAChD,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC9B,GAAG,CAAC,KAAK,CAAC,QAAQ,IAAI,sEAAsE,CAAC,CAAC;IAChG,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,wBAAwB,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|