baton-auth 0.1.3__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.
@@ -0,0 +1,107 @@
1
+ name: Package
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*"]
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ test-and-build:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.10", "3.12", "3.14"]
20
+
21
+ steps:
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+
24
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
25
+ with:
26
+ version: "0.13.5"
27
+
28
+ - name: Install package and test dependencies
29
+ run: uv sync --locked --extra dev --python ${{ matrix.python-version }}
30
+
31
+ - name: Test
32
+ run: uv run --no-sync pytest tests -q
33
+
34
+ - name: Build source archive and wheel
35
+ run: uv build
36
+
37
+ - name: Install and run the wheel in a clean environment
38
+ run: |
39
+ uv venv .package-venv --python ${{ matrix.python-version }}
40
+ uv pip install --python .package-venv/bin/python dist/*.whl
41
+ .package-venv/bin/python -m baton_auth --version
42
+ .package-venv/bin/baton-auth --version
43
+ .package-venv/bin/python -c 'import baton_auth, importlib.metadata; assert baton_auth.__version__ == importlib.metadata.version("baton-auth")'
44
+ .package-venv/bin/baton setup --state-dir "$RUNNER_TEMP/baton-smoke" > .package-venv/mcp-config.json
45
+ .package-venv/bin/python -c 'import json; from pathlib import Path; assert json.loads(Path(".package-venv/mcp-config.json").read_text())["mcpServers"]["baton"]["args"] == ["-m", "baton_auth.mcp_server"]'
46
+ .package-venv/bin/baton setup --client muse-code --output .package-venv/muse-settings.json --state-dir "$RUNNER_TEMP/baton-smoke"
47
+ .package-venv/bin/baton setup --client hermes-agent --output .package-venv/hermes-config.yaml --state-dir "$RUNNER_TEMP/baton-smoke"
48
+ .package-venv/bin/python -c 'import json; from pathlib import Path; from ruamel.yaml import YAML; assert json.loads(Path(".package-venv/muse-settings.json").read_text())["mcpServers"]["baton"]["type"] == "stdio"; assert YAML().load(Path(".package-venv/hermes-config.yaml").read_text())["mcp_servers"]["baton"]["command"]'
49
+
50
+ release:
51
+ if: startsWith(github.ref, 'refs/tags/v')
52
+ needs: test-and-build
53
+ runs-on: ubuntu-latest
54
+ permissions:
55
+ contents: write
56
+
57
+ steps:
58
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
59
+
60
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
61
+ with:
62
+ version: "0.13.5"
63
+
64
+ - name: Check that the tag matches the package version
65
+ run: test "v$(uv version --short)" = "$GITHUB_REF_NAME"
66
+
67
+ - name: Build release files
68
+ run: |
69
+ uv build --no-sources
70
+ sha256sum dist/*.whl dist/*.tar.gz > dist/SHA256SUMS
71
+
72
+ - name: Publish GitHub release
73
+ env:
74
+ GH_TOKEN: ${{ github.token }}
75
+ run: gh release create "$GITHUB_REF_NAME" dist/*.whl dist/*.tar.gz dist/SHA256SUMS --verify-tag --generate-notes
76
+
77
+ - name: Save distributions for PyPI
78
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
79
+ with:
80
+ name: pypi-distributions
81
+ path: |
82
+ dist/*.whl
83
+ dist/*.tar.gz
84
+ if-no-files-found: error
85
+
86
+ publish-pypi:
87
+ if: startsWith(github.ref, 'refs/tags/v')
88
+ needs: release
89
+ runs-on: ubuntu-latest
90
+ environment: pypi
91
+ permissions:
92
+ id-token: write
93
+
94
+ steps:
95
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
96
+ with:
97
+ version: "0.13.5"
98
+ enable-cache: false
99
+
100
+ - name: Download distributions
101
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
102
+ with:
103
+ name: pypi-distributions
104
+ path: dist/
105
+
106
+ - name: Publish to PyPI
107
+ run: uv publish --trusted-publishing always dist/*.whl dist/*.tar.gz
@@ -0,0 +1,9 @@
1
+ .DS_Store
2
+ __pycache__/
3
+ *.pyc
4
+ .venv/
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .orka/
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.5
2
+ Name: baton-auth
3
+ Version: 0.1.3
4
+ Summary: Local authentication broker for AI agents and MCP tools
5
+ Project-URL: Homepage, https://github.com/DanDekker/baton
6
+ Project-URL: Documentation, https://github.com/DanDekker/baton/blob/main/docs/guide.md
7
+ Project-URL: Issues, https://github.com/DanDekker/baton/issues
8
+ Project-URL: Source, https://github.com/DanDekker/baton
9
+ Keywords: AI agents,MCP,authentication,credential management,secrets management
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Security
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: argon2-cffi>=25
17
+ Requires-Dist: blake3>=1.0
18
+ Requires-Dist: cryptography>=45
19
+ Requires-Dist: httpx>=0.27
20
+ Requires-Dist: keyring>=24
21
+ Requires-Dist: mcp>=2.0
22
+ Requires-Dist: ruamel-yaml>=0.18.16
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=8; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ <div align="center">
28
+
29
+ <img src="https://raw.githubusercontent.com/DanDekker/baton/main/assets/baton-banner.svg" alt="baton — Pass the baton, never the secret." width="100%">
30
+
31
+ <p><a href="#try-it">Try it</a> · <a href="https://github.com/DanDekker/baton/blob/main/docs/guide.md">User guide</a> · <a href="https://github.com/DanDekker/baton/blob/main/docs/security.md">Security</a></p>
32
+
33
+ <p><a href="https://github.com/DanDekker/baton/actions/workflows/package.yml"><img src="https://github.com/DanDekker/baton/actions/workflows/package.yml/badge.svg?branch=main" alt="Package CI"></a></p>
34
+
35
+ </div>
36
+
37
+ ---
38
+
39
+ **baton is a local authentication broker for AI agents and MCP tools.** It lets an agent use your GitHub, cloud, or API credentials without putting the secret value into its chat or tool output.
40
+
41
+ Save a credential once, group it into a **context** such as `work`, then give an agent a **lease**: a temporary pass that can expire or be revoked. baton uses the credential for a command or HTTP request and hides known secrets in the result.
42
+
43
+ ## Try it
44
+
45
+ Requires Python 3.10+ and [uv](https://docs.astral.sh/uv/). Install the package from GitHub:
46
+
47
+ ```bash
48
+ uv tool install git+https://github.com/DanDekker/baton.git
49
+
50
+ baton cred add gh-work --provider github --field token --context work
51
+
52
+ baton run work -- gh api /user
53
+ baton http work https://api.github.com/user
54
+ baton lease new work --ttl 600 --uses 20
55
+ baton setup # prints MCP config for an agent client
56
+ ```
57
+
58
+ The bare `--field token` prompts privately. Commands run with a temporary `HOME` containing that context's CLI settings.
59
+
60
+ `baton setup --client CLIENT` connects Codex, Claude Code, Cursor, VS Code, Muse Code, or Hermes Agent. `baton setup` prints portable MCP config for other clients. Agents with command access can use `baton run` or `baton http`.
61
+
62
+ ## Cryptography at a glance
63
+
64
+ | Name | How baton uses it |
65
+ | --- | --- |
66
+ | AES-256-GCM-SIV; ChaCha20-Poly1305 fallback | Encrypts credentials in the local vault. |
67
+ | BLAKE3 | Derives separate keys, authenticates leases and vault records, and links audit entries. |
68
+ | Argon2id | Turns a passphrase into the vault key when passphrase mode is used. |
69
+ | HMAC-SHA-256 | Signs AWS requests using Signature Version 4. |
70
+
71
+ ## Learn more
72
+
73
+ - [User guide](https://github.com/DanDekker/baton/blob/main/docs/guide.md) — contexts, leases, providers, refresh, and MCP tools.
74
+ - [Security and limits](https://github.com/DanDekker/baton/blob/main/docs/security.md) — what baton protects, how it stores secrets, and where its boundary ends.
75
+
76
+ The distribution is named `baton-auth` and also provides the `baton-auth` command. The Python import is `baton_auth`.
77
+
78
+ baton runs locally for one user on one machine. A temporary `HOME` keeps CLI configuration separate; it is not a full operating-system sandbox.
@@ -0,0 +1,52 @@
1
+ <div align="center">
2
+
3
+ <img src="https://raw.githubusercontent.com/DanDekker/baton/main/assets/baton-banner.svg" alt="baton — Pass the baton, never the secret." width="100%">
4
+
5
+ <p><a href="#try-it">Try it</a> · <a href="https://github.com/DanDekker/baton/blob/main/docs/guide.md">User guide</a> · <a href="https://github.com/DanDekker/baton/blob/main/docs/security.md">Security</a></p>
6
+
7
+ <p><a href="https://github.com/DanDekker/baton/actions/workflows/package.yml"><img src="https://github.com/DanDekker/baton/actions/workflows/package.yml/badge.svg?branch=main" alt="Package CI"></a></p>
8
+
9
+ </div>
10
+
11
+ ---
12
+
13
+ **baton is a local authentication broker for AI agents and MCP tools.** It lets an agent use your GitHub, cloud, or API credentials without putting the secret value into its chat or tool output.
14
+
15
+ Save a credential once, group it into a **context** such as `work`, then give an agent a **lease**: a temporary pass that can expire or be revoked. baton uses the credential for a command or HTTP request and hides known secrets in the result.
16
+
17
+ ## Try it
18
+
19
+ Requires Python 3.10+ and [uv](https://docs.astral.sh/uv/). Install the package from GitHub:
20
+
21
+ ```bash
22
+ uv tool install git+https://github.com/DanDekker/baton.git
23
+
24
+ baton cred add gh-work --provider github --field token --context work
25
+
26
+ baton run work -- gh api /user
27
+ baton http work https://api.github.com/user
28
+ baton lease new work --ttl 600 --uses 20
29
+ baton setup # prints MCP config for an agent client
30
+ ```
31
+
32
+ The bare `--field token` prompts privately. Commands run with a temporary `HOME` containing that context's CLI settings.
33
+
34
+ `baton setup --client CLIENT` connects Codex, Claude Code, Cursor, VS Code, Muse Code, or Hermes Agent. `baton setup` prints portable MCP config for other clients. Agents with command access can use `baton run` or `baton http`.
35
+
36
+ ## Cryptography at a glance
37
+
38
+ | Name | How baton uses it |
39
+ | --- | --- |
40
+ | AES-256-GCM-SIV; ChaCha20-Poly1305 fallback | Encrypts credentials in the local vault. |
41
+ | BLAKE3 | Derives separate keys, authenticates leases and vault records, and links audit entries. |
42
+ | Argon2id | Turns a passphrase into the vault key when passphrase mode is used. |
43
+ | HMAC-SHA-256 | Signs AWS requests using Signature Version 4. |
44
+
45
+ ## Learn more
46
+
47
+ - [User guide](https://github.com/DanDekker/baton/blob/main/docs/guide.md) — contexts, leases, providers, refresh, and MCP tools.
48
+ - [Security and limits](https://github.com/DanDekker/baton/blob/main/docs/security.md) — what baton protects, how it stores secrets, and where its boundary ends.
49
+
50
+ The distribution is named `baton-auth` and also provides the `baton-auth` command. The Python import is `baton_auth`.
51
+
52
+ baton runs locally for one user on one machine. A temporary `HOME` keeps CLI configuration separate; it is not a full operating-system sandbox.
@@ -0,0 +1,56 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 300" role="img" aria-labelledby="title desc">
2
+ <title id="title">baton</title>
3
+ <desc id="desc">Auth contexts for agents. Pass the baton, never the secret.</desc>
4
+ <defs>
5
+ <linearGradient id="background" x1="0" y1="0" x2="1" y2="1">
6
+ <stop offset="0" stop-color="#050505"/>
7
+ <stop offset="1" stop-color="#0d0d0d"/>
8
+ </linearGradient>
9
+ <radialGradient id="glow" cx="0" cy="0" r="1" gradientTransform="translate(1000 78) rotate(90) scale(260 350)">
10
+ <stop stop-color="#ffffff" stop-opacity=".04"/>
11
+ <stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
12
+ </radialGradient>
13
+ <linearGradient id="gold-body" x1="0" y1="0" x2="1" y2="0">
14
+ <stop offset="0" stop-color="#744414"/>
15
+ <stop offset=".18" stop-color="#c88b31"/>
16
+ <stop offset=".42" stop-color="#fff1ba"/>
17
+ <stop offset=".61" stop-color="#e5ad50"/>
18
+ <stop offset="1" stop-color="#8f581b"/>
19
+ </linearGradient>
20
+ <radialGradient id="gold-cap">
21
+ <stop stop-color="#fff6d1"/>
22
+ <stop offset=".65" stop-color="#dfaa4e"/>
23
+ <stop offset="1" stop-color="#8f5518"/>
24
+ </radialGradient>
25
+ <filter id="baton-shadow" x="-70%" y="-30%" width="240%" height="170%">
26
+ <feGaussianBlur stdDeviation="5"/>
27
+ </filter>
28
+ <clipPath id="frame">
29
+ <rect width="1200" height="300" rx="22"/>
30
+ </clipPath>
31
+ </defs>
32
+
33
+ <g clip-path="url(#frame)">
34
+ <rect width="1200" height="300" fill="url(#background)"/>
35
+ <rect width="1200" height="300" fill="url(#glow)"/>
36
+ <path d="M785 0v300M865 0v300M945 0v300M1025 0v300M1105 0v300" stroke="#ffffff" stroke-opacity=".06"/>
37
+ <path d="M740 72h398M740 150h398M740 228h398" stroke="#ffffff" stroke-opacity=".06"/>
38
+
39
+ <rect x="67" y="76" width="144" height="144" rx="34" fill="#252525" stroke="#484848" stroke-width="2"/>
40
+ <g transform="translate(139 148) rotate(45)">
41
+ <rect x="-16" y="-59" width="37" height="119" rx="16" fill="#000000" opacity=".5" filter="url(#baton-shadow)"/>
42
+ <rect x="-15" y="-58" width="30" height="116" rx="14" fill="url(#gold-body)" stroke="#6c3f11" stroke-width="1.5"/>
43
+ <path d="M-7 -43v86" stroke="#fff6d6" stroke-opacity=".65" stroke-width="2" stroke-linecap="round"/>
44
+ <path d="M9 -44v88" stroke="#5d340d" stroke-opacity=".35" stroke-width="3" stroke-linecap="round"/>
45
+ <ellipse cx="0" cy="-55" rx="14" ry="6" fill="url(#gold-cap)"/>
46
+ <ellipse cx="0" cy="55" rx="14" ry="6" fill="url(#gold-cap)"/>
47
+ </g>
48
+
49
+ <text x="252" y="94" fill="#b9b9b9" font-family="-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="16" font-weight="700" letter-spacing="3.5">AUTH CONTEXTS FOR AGENTS</text>
50
+ <text x="246" y="184" fill="#f6f6f6" font-family="-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="82" font-weight="750" letter-spacing="-3">baton</text>
51
+ <text x="251" y="229" fill="#c8c8c8" font-family="-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="25">Pass the baton, never the secret.</text>
52
+
53
+ <path d="M849 121l47-47m-47 126l125-126m-47 126l125-126m-47 126l125-126" stroke="#ffffff" stroke-opacity=".07" stroke-width="16" stroke-linecap="round"/>
54
+ <path d="M0 298h1200" stroke="#ffffff" stroke-opacity=".14" stroke-width="4"/>
55
+ </g>
56
+ </svg>
@@ -0,0 +1,124 @@
1
+ # Using baton
2
+
3
+ [Back to overview](../README.md) · [Security and limits](security.md)
4
+
5
+ baton keeps credentials on your machine and uses them on an agent's behalf. Three terms are enough to get started:
6
+
7
+ | Term | What it means |
8
+ | --- | --- |
9
+ | **Credential** | A token, password, key, or config file you store in baton. |
10
+ | **Context** | A named group of credentials, such as `work` or `staging`. |
11
+ | **Lease** | A temporary pass to use a context. It can expire, run out of uses, or be revoked. |
12
+
13
+ ## Set up a context
14
+
15
+ Install with Python 3.10+ and [uv](https://docs.astral.sh/uv/), then add a GitHub token. A bare `--field token` prompts without showing the value or putting it in shell history.
16
+
17
+ ```bash
18
+ uv tool install git+https://github.com/DanDekker/baton.git
19
+
20
+ baton cred add gh-work --provider github --field token --context work
21
+ baton ctx list
22
+ ```
23
+
24
+ Run a command or send an HTTP request with the `work` context:
25
+
26
+ ```bash
27
+ baton run work -- gh api /user
28
+ baton http work https://api.github.com/user
29
+ ```
30
+
31
+ For `baton run`, put baton options before the context name and the child command after `--`. The child gets a temporary `HOME` with this context's CLI configuration. Output is checked for known secrets before it is returned.
32
+
33
+ ## Give an agent temporary access
34
+
35
+ ```bash
36
+ baton lease new work --ttl 600 --uses 20
37
+ baton lease list
38
+ baton lease revoke LEASE_ID
39
+ ```
40
+
41
+ The first command prints a lease token. Give that token only to the agent that needs it: anyone holding a live lease can use its allowed context. `--ttl` is in seconds; `--uses` sets a use limit. Add `--service github` to restrict a lease to that service.
42
+
43
+ ## Connect an MCP client
44
+
45
+ Connect a local agent with one command:
46
+
47
+ ```bash
48
+ baton setup --client codex
49
+ baton setup --client claude-code
50
+ baton setup --client cursor
51
+ baton setup --client vscode
52
+ baton setup --client muse-code
53
+ baton setup --client hermes-agent
54
+ ```
55
+
56
+ Codex and Claude Code use their own setup commands. Cursor gets a personal `~/.cursor/mcp.json`; VS Code gets `.vscode/mcp.json` in the current project. For another MCP client, run `baton setup` to print JSON, or `baton setup --output PATH` to merge into its JSON config. Add `--root servers` if that client uses a `servers` key. baton uses its installed Python environment and includes the vault path, so the client opens the same vault. Setup leaves other configured servers alone.
57
+
58
+ Muse Code gets `~/.config/muse/settings.json` (or `$XDG_CONFIG_HOME/muse/settings.json`); Hermes Agent gets `~/.hermes/config.yaml` (or `$HERMES_HOME/config.yaml`). Both keep their existing settings and other MCP servers. Use `--output PATH` to target another config file.
59
+
60
+ Client formats follow the current [Codex](https://learn.chatgpt.com/docs/extend/mcp), [Claude Code](https://code.claude.com/docs/en/mcp), [Cursor](https://cursor.com/docs/mcp), [VS Code](https://code.visualstudio.com/docs/agents/reference/mcp-configuration), [Muse Code](https://meta-models.github.io/muse-code-sdk/next/guides/extend/mcp-servers/), and [Hermes Agent](https://github.com/NousResearch/hermes-agent/blob/main/website/docs/reference/mcp-config-reference.md) MCP guides.
61
+
62
+ If you use `BATON_MASTER_KEY` or `BATON_PASSPHRASE`, supply it to the client through its environment; setup never writes a key into the config. Agents without MCP can call `baton run` or `baton http` through a command tool. A local MCP server must be installed on the machine where the agent runs.
63
+
64
+ | What an agent needs to do | MCP tools |
65
+ | --- | --- |
66
+ | See available contexts and metadata | `baton_contexts`, `baton_credentials`, `baton_info` |
67
+ | Get or manage a pass | `baton_lease`, `baton_leases`, `baton_revoke` |
68
+ | Use a context | `baton_run`, `baton_http` |
69
+ | Refresh or inspect history | `baton_refresh`, `baton_audit` |
70
+
71
+ These tools do not return stored credential values. The server uses MCP Python SDK 2.x.
72
+
73
+ ## Use other services
74
+
75
+ Run `baton info` to see the fields expected by each preset. Built-in presets are `aws`, `gcp`, `github`, `gitlab`, `npm`, `docker`, `kube`, `ssh`, and `generic`. Any other provider name uses bearer token auth by default.
76
+
77
+ ```bash
78
+ baton cred add kube-prod --provider kube --field-file kubeconfig=~/.kube/config
79
+ baton cred add registry --provider docker \
80
+ --field username --field password --field registry=ghcr.io
81
+ baton cred add my-api --provider my-service --field api_key \
82
+ --auth header:x-api-key --auth-field api_key \
83
+ --env 'MY_SERVICE_KEY={api_key}' --context work
84
+ ```
85
+
86
+ Presets decide how baton sets environment variables, writes CLI config files, or attaches HTTP authentication. For a custom provider, `--auth` also accepts `bearer`, `query:NAME`, `basic` (with `username` and `password` fields), and `none` for CLI-only use. Use `--field-file NAME=PATH` for a file or longer value. Avoid `--field NAME=VALUE` for secrets because the value can appear in shell history and process listings.
87
+
88
+ ## Refresh expiring tokens
89
+
90
+ baton can refresh an expiring credential before use. OAuth 2.0 refresh tokens, external commands, and custom Python handlers are supported. For OAuth, keep the client secret and refresh token in prompted fields:
91
+
92
+ ```bash
93
+ baton cred add api --provider generic \
94
+ --field token --field refresh_token --field client_secret \
95
+ --refresh-provider oauth2 \
96
+ --refresh-token-url https://auth.example.com/token \
97
+ --refresh-client-id my-client-id
98
+
99
+ baton cred refresh api --force
100
+ ```
101
+
102
+ OAuth refresh can rotate the refresh token and update the expiry from `expires_in`. Python integrations can register other handlers with `register_refresh_provider(name, handler)`.
103
+
104
+ ## Useful commands
105
+
106
+ | Command | Purpose |
107
+ | --- | --- |
108
+ | `baton status` | Show vault status and counts. |
109
+ | `baton cred list` / `baton ctx list` | See stored names without revealing values. |
110
+ | `baton env work` | See which environment variables a context sets. |
111
+ | `baton audit` | Check the audit chain and recent activity. |
112
+ | `baton --help` | See all commands and examples. |
113
+
114
+ Use `--state-dir PATH` to choose another vault directory. `baton env --reveal` prints values for a human operator on explicit request; never use it in an agent transcript.
115
+
116
+ ## Work on the package
117
+
118
+ The distribution name is `baton-auth` because `baton` is an unrelated PyPI project. The import name is `baton_auth`. This repository installs the `baton`, `baton-auth`, and `baton-mcp` commands. It is installable from GitHub; a PyPI release has not been published yet.
119
+
120
+ ```bash
121
+ uv sync --extra dev
122
+ .venv/bin/python -m pytest tests -q
123
+ uv build
124
+ ```
@@ -0,0 +1,40 @@
1
+ # Security and limits
2
+
3
+ [Back to overview](../README.md) · [User guide](guide.md)
4
+
5
+ baton keeps stored credential values away from MCP tools. Agents can see context names and use a temporary lease, while baton applies the real credential to a command or HTTP request. The returned output is checked for known secret values and recognized token shapes.
6
+
7
+ ## What baton protects
8
+
9
+ | Area | How it works |
10
+ | --- | --- |
11
+ | Vault | Credential records are encrypted and tied to their record identity. The database file alone cannot decrypt them. |
12
+ | Leases | Handles expire, can be revoked or limited by service and use count, and are stored only as token digests. |
13
+ | CLI commands | Each run gets a temporary `0700` home with its context's auth files. Common inherited code injection variables are removed. |
14
+ | HTTP requests | baton attaches bearer, header, basic, query, or AWS SigV4 auth. It rejects disallowed URL schemes, metadata endpoints, link-local addresses, and private networks by default. Redirects are returned without being followed. |
15
+ | Results | Known secret values and common provider token patterns are redacted before a broker result is returned. |
16
+ | Audit | Actions are recorded in a BLAKE3 hash chain. SQLite uses WAL and atomic lease use updates. |
17
+
18
+ The master seed is resolved in this order: `BATON_MASTER_KEY` → `BATON_PASSPHRASE` with Argon2id → OS keyring → a `0600` keyfile. Mutable key buffers are wiped when the vault closes.
19
+
20
+ ## Where the boundary ends
21
+
22
+ - baton is local to one user on one machine. It does not sync vaults between hosts.
23
+ - A temporary `HOME` separates CLI settings; it is not an operating-system sandbox. Child processes keep their normal filesystem permissions.
24
+ - A lease is a bearer token. Share it only with the intended agent and revoke it when it is no longer needed.
25
+ - Redaction is based on known values and token patterns. It cannot undo a leak that happened through another tool or guarantee that every possible encoding is caught.
26
+ - Hostnames are checked before HTTP connections. DNS can change between validation and connection; use network-level egress controls for untrusted destinations.
27
+ - `baton env --reveal` prints values when an operator explicitly asks for them. MCP has no reveal tool.
28
+
29
+ ## Cryptographic construction
30
+
31
+ | Purpose | Primitive |
32
+ | --- | --- |
33
+ | Record encryption | AES-256-GCM-SIV (RFC 8452) when available; ChaCha20-Poly1305 fallback |
34
+ | Envelope commitment | BLAKE3 keyed tag over header, associated data, and ciphertext |
35
+ | Subkeys | BLAKE3 `derive_key`, separated by purpose |
36
+ | Lease MACs and audit chain | BLAKE3 keyed mode |
37
+ | Passphrase derivation | Argon2id, 64 MiB memory, 3 iterations, parallelism 4 |
38
+ | Record identity | Associated data `cred:<id>:<name>` |
39
+
40
+ AWS SigV4 uses SHA-256 as required by that protocol. The implementation lives in [`src/baton_auth/crypto.py`](../src/baton_auth/crypto.py), [`src/baton_auth/vault.py`](../src/baton_auth/vault.py), and [`src/baton_auth/broker.py`](../src/baton_auth/broker.py).
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "baton-auth"
7
+ description = "Local authentication broker for AI agents and MCP tools"
8
+ version = "0.1.3"
9
+ requires-python = ">=3.10"
10
+ readme = "README.md"
11
+ keywords = ["AI agents", "authentication", "MCP", "credential management", "secrets management"]
12
+ classifiers = [
13
+ "Environment :: Console",
14
+ "Intended Audience :: Developers",
15
+ "Programming Language :: Python :: 3",
16
+ "Topic :: Security",
17
+ "Topic :: Software Development :: Libraries :: Python Modules",
18
+ ]
19
+ dependencies = [
20
+ "cryptography>=45",
21
+ "blake3>=1.0",
22
+ "argon2-cffi>=25",
23
+ "keyring>=24",
24
+ "mcp>=2.0",
25
+ "httpx>=0.27",
26
+ "ruamel.yaml>=0.18.16",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/DanDekker/baton"
31
+ Documentation = "https://github.com/DanDekker/baton/blob/main/docs/guide.md"
32
+ Issues = "https://github.com/DanDekker/baton/issues"
33
+ Source = "https://github.com/DanDekker/baton"
34
+
35
+ [project.scripts]
36
+ baton = "baton_auth.cli:main"
37
+ baton-auth = "baton_auth.cli:main"
38
+ baton-mcp = "baton_auth.mcp_server:main"
39
+
40
+ [project.optional-dependencies]
41
+ dev = ["pytest>=8"]
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/baton_auth"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
@@ -0,0 +1,45 @@
1
+ """baton: pass auth context between agents as leases, never as secrets."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ from .crypto import CryptoError, zeroize
6
+ from .models import (
7
+ BatonError,
8
+ Context,
9
+ Credential,
10
+ Lease,
11
+ LeaseError,
12
+ RefreshError,
13
+ UnknownContext,
14
+ UnknownCredential,
15
+ VaultLocked,
16
+ )
17
+
18
+ try:
19
+ __version__ = version("baton-auth")
20
+ except PackageNotFoundError: # running from an unpackaged source tree
21
+ __version__ = "0+local"
22
+
23
+ __all__ = [
24
+ "BatonError",
25
+ "Context",
26
+ "Credential",
27
+ "CryptoError",
28
+ "Lease",
29
+ "LeaseError",
30
+ "RefreshError",
31
+ "UnknownContext",
32
+ "UnknownCredential",
33
+ "VaultLocked",
34
+ "Vault",
35
+ "zeroize",
36
+ "__version__",
37
+ ]
38
+
39
+
40
+ def __getattr__(name: str):
41
+ if name == "Vault":
42
+ from .vault import Vault
43
+
44
+ return Vault
45
+ raise AttributeError(name)
@@ -0,0 +1,7 @@
1
+ """Run the operator CLI with ``python -m baton_auth``."""
2
+
3
+ from .cli import main
4
+
5
+
6
+ if __name__ == "__main__":
7
+ raise SystemExit(main())