From b35a17166608ba0ab2148f8803c3e547b87b79ab Mon Sep 17 00:00:00 2001 From: Divine Date: Mon, 29 Jun 2026 18:14:20 -0400 Subject: [PATCH 1/3] docs: fix WRONG+HIGH tier doc-accuracy audit findings Correct nine shipped docs against current code and the live Python SDK (PyPI agentwrit v0.3.0), per the 2026-06-07 doc-accuracy audit. WRONG tier: - demos.md: replace coming-soon banner with live docker-compose run steps and the three published image names - python-sdk.md: drop fabricated register(launch_token=) API, document the real AgentWritApp/create_agent surface - faq.md: remove enterprise-module/open-core framing (Decision 022); mark SDK + demos shipped; event count 24 -> 25 HIGH tier: - common-tasks.md: sanitized validate-invalid response (no detail/codes); de-hardcode iss example - foundations.md: iss is config-driven (AA_ISSUER, empty default skips check), not always 'agentwrit' - SECURITY.md: add GitHub Security Advisory channel; real version table - design-decisions.md: planned OIDC bridge behind interface seam, not add-on - api.md: app/auth rate limit 10/min burst 3; event count 23 -> 25; add sid - openapi.yaml: details->detail + 8 missing event fields; policy fields; real SPIFFE shape Verified every fix against source; corrected two stale audit claims (event count is 25 not 26; release 403 insufficient_scope is correct). Co-authored-by: Claude --- SECURITY.md | 9 +++--- docs/api.md | 11 ++++---- docs/api/openapi.yaml | 38 ++++++++++++++++++++----- docs/common-tasks.md | 31 +++++++++++---------- docs/demos.md | 45 +++++++++++++++++++++++------- docs/design-decisions.md | 4 +-- docs/faq.md | 25 +++++++++-------- docs/foundations.md | 6 ++-- docs/python-sdk.md | 60 +++++++++++++++++++++++++++------------- 9 files changed, 153 insertions(+), 76 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index c970185..c919b0d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,10 @@ ## Reporting a Vulnerability -If you discover a security vulnerability in AgentWrit, please report it responsibly to **security@agentwrit.com** rather than using public issue trackers. +If you discover a security vulnerability in AgentWrit, please report it responsibly through a private channel rather than using public issue trackers: + +1. **Preferred — [GitHub Security Advisory](https://github.com/devonartis/agentwrit/security/advisories/new):** open a private advisory directly on the repository. This keeps the report private to maintainers and lets us coordinate a fix and disclosure in one place. +2. **Email:** **security@agentwrit.com** if you prefer not to use GitHub. **We ask that you:** - Provide detailed information about the vulnerability @@ -18,10 +21,8 @@ All vulnerability reports are taken seriously and will be investigated promptly. | Version | Status | Security Updates | |---------|--------|------------------| | 2.0.x | Current | Yes | -| 1.x | EOL | No | -| < 1.0 | Unsupported | No | -Users should upgrade to 2.0.x to receive security patches. Version 1.x is no longer supported and will not receive updates. +`2.0.0` is the only tagged release. Security patches land on the `2.0.x` line; run the latest `2.0.x` to receive them. Earlier version lines were never published. ## What Constitutes a Security Vulnerability diff --git a/docs/api.md b/docs/api.md index 4ab51cf..3c43bc0 100644 --- a/docs/api.md +++ b/docs/api.md @@ -42,7 +42,7 @@ Authentication endpoints are rate-limited to protect against brute-force attacks | Endpoint | Rate | Burst | Key | Retry-After | |----------|------|-------|-----|-------------| | `POST /v1/admin/auth` | 5 req/s | 10 | Per IP address | 1 second | -| `POST /v1/app/auth` | Configurable | Configurable | Per `client_id` (falls back to IP) | 60 seconds | +| `POST /v1/app/auth` | 10 req/min | 3 | Per `client_id` (falls back to IP) | 60 seconds | When a rate limit is exceeded, the broker returns `429 Too Many Requests` in RFC 7807 Problem Details format with a `Retry-After` header. @@ -225,7 +225,7 @@ Verify a token and return its claims. Also checks revocation status. { "valid": true, "claims": { - "iss": "agentwrit", + "iss": "https://broker.example.com", "sub": "spiffe://agentwrit.local/agent/orch/task/instance", "exp": 1707600000, "nbf": 1707599700, @@ -233,7 +233,8 @@ Verify a token and return its claims. Also checks revocation status. "jti": "a1b2c3d4e5f67890...", "scope": ["read:data:*"], "task_id": "task-001", - "orch_id": "my-orchestrator" + "orch_id": "my-orchestrator", + "sid": "" } } ``` @@ -678,7 +679,7 @@ Each `AuditEvent`: |---|---|---| | `id` | string | Sequential ID (`evt-000001`) | | `timestamp` | string | RFC3339 timestamp | -| `event_type` | string | One of 23 event types | +| `event_type` | string | One of 25 event types | | `agent_id` | string | Agent SPIFFE ID (if applicable) | | `task_id` | string | Task ID (if applicable) | | `orch_id` | string | Orchestration ID (if applicable) | @@ -691,7 +692,7 @@ Each `AuditEvent`: | `hash` | string | SHA-256 hex hash of this event | | `prev_hash` | string | SHA-256 hex hash of the previous event | -The 23 event types include the original lifecycle events (`admin_auth`, `agent_registered`, `token_issued`, `token_revoked`, `token_renewed`, `delegation_created`, etc.) plus 6 enforcement audit events: +The 25 event types span the lifecycle events (`admin_auth`, `agent_registered`, `token_issued`, `token_revoked`, `token_renewed`, `delegation_created`, etc.), the app-management events (`app_registered`, `app_authenticated`, `app_updated`, …), and the enforcement audit events below: | Event Type | Description | |---|---| diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index c37e184..3b72853 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -699,8 +699,10 @@ components: properties: agent_id: type: string - description: SPIFFE ID assigned to the agent - example: spiffe://agentwrit.local/agent/agent-12345 + description: > + SPIFFE ID assigned to the agent, in the form + spiffe://{trust_domain}/agent/{orch_id}/{task_id}/{instance_id}. + example: spiffe://agentwrit.local/agent/orch-001/task-analyze-q4/proc-abc123 access_token: type: string description: JWT bearer token for the agent @@ -899,6 +901,9 @@ components: items: type: object properties: + id: + type: string + description: Sequential event ID (e.g. evt-000001) timestamp: type: string format: date-time @@ -908,11 +913,32 @@ components: type: string task_id: type: string + orch_id: + type: string + detail: + type: string + description: Human-readable description (PII-sanitized) + resource: + type: string + description: Target resource path (e.g. API endpoint) outcome: type: string enum: [success, denied] - details: + deleg_depth: + type: integer + description: Delegation chain depth (0 = direct) + deleg_chain_hash: type: string + description: SHA-256 hash of the delegation chain + bytes_transferred: + type: integer + description: Bytes transferred (for metered operations) + hash: + type: string + description: SHA-256 hex hash of this event + prev_hash: + type: string + description: SHA-256 hex hash of the previous event total: type: integer description: Total number of events matching the query @@ -998,16 +1024,14 @@ components: type: object description: Policy attached to the launch token properties: - agent_name: - type: string allowed_scope: type: array items: type: string - ttl: - type: integer max_ttl: type: integer + single_use: + type: boolean # ========================================================================= # App Registration diff --git a/docs/common-tasks.md b/docs/common-tasks.md index f2b76f5..98532d9 100644 --- a/docs/common-tasks.md +++ b/docs/common-tasks.md @@ -226,7 +226,7 @@ except requests.exceptions.RequestException as e: { "valid": true, "claims": { - "iss": "agentwrit", + "iss": "https://broker.example.com", "sub": "spiffe://agentwrit.local/agent/orch-001/task-analyze-q4/proc-abc123", "exp": 1745405630, "iat": 1745405330, @@ -243,11 +243,12 @@ except requests.exceptions.RequestException as e: ```json { "valid": false, - "error": "token_revoked", - "detail": "Token JTI a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 was revoked at 2026-02-15T10:00:00Z" + "error": "token is invalid or expired" } ``` +The invalid response is deliberately **sanitized**: a single opaque `error` string, no `detail` and no distinct error code. Expired, revoked, and bad-signature tokens are indistinguishable to the caller — the broker won't tell a holder *why* a token failed. + **TypeScript/Node.js example:** ```typescript @@ -268,9 +269,8 @@ interface ClaimSet { interface ValidationResponse { valid: boolean; - claims?: ClaimSet; - error?: string; - detail?: string; + claims?: ClaimSet; // present only when valid + error?: string; // present only when invalid (opaque, sanitized) } async function validateToken(broker: string, token: string): Promise { @@ -287,7 +287,7 @@ async function validateToken(broker: string, token: string): Promise { const result = (await response.json()) as ValidationResponse; if (!result.valid) { - throw new Error(`Token invalid: ${result.error} - ${result.detail}`); + throw new Error(`Token invalid: ${result.error}`); } return result.claims!; @@ -327,13 +327,16 @@ try { **If this fails:** -| Issue | Meaning | Action | -|-------|---------|--------| -| Broker unreachable | Network connectivity issue | Verify broker is running; check firewall rules | -| Invalid token format | Token is malformed | Ensure you're passing the full JWT string | -| `token_expired` | Token timestamp has passed | Renew the token or re-register to get a fresh one | -| `token_revoked` | Token was revoked by operator | Re-register to get a fresh token; investigate with operator | -| `invalid_signature` | JWT signature verification failed | Token may be corrupted or forged; obtain new one | +A `200 OK` with `"valid": false` covers every token-rejection reason — the response does **not** distinguish them. The reasons below are why a token *can* be invalid, not values you'll see in the response. Re-register to get a fresh token; an operator can confirm the cause from the audit trail. + +| Reason (not distinguishable in the response) | Meaning | +|-------|---------| +| Expired | Token timestamp has passed | +| Revoked | Token was revoked by an operator | +| Bad signature | JWT signature verification failed (corrupted or forged) | +| Malformed | Not a parseable JWT — ensure you pass the full token string | + +A network error (broker unreachable) surfaces separately as a failed HTTP request, not as `valid: false`. --- diff --git a/docs/demos.md b/docs/demos.md index c6375cd..2097b2d 100644 --- a/docs/demos.md +++ b/docs/demos.md @@ -1,28 +1,53 @@ # AgentWrit Demos -> **Coming soon.** The demo applications ship with the Python SDK and will be available when [`devonartis/agentwrit-python`](https://github.com/devonartis/agentwrit-python) goes public. +Two complete demo apps ship with the [Python SDK repo](https://github.com/devonartis/agentwrit-python) (`demo/` and `demo2/`). Both run against a live broker via Docker Compose and published images. Each spawns real broker agents with per-task scoped credentials so you can watch scope enforcement, delegation, and TTL expiry on real traffic. + +## Run them + +The Compose file lives in the SDK repo. It pulls three published images — the broker plus both demos — so nothing needs building: + +| Service | Image | Port | +|---|---|---| +| Broker | `devonartis/agentwrit:latest` | 8080 | +| MedAssist | `devonartis/agentwrit-medassist:latest` | 5000 | +| Support Tickets | `devonartis/agentwrit-support-tickets:latest` | 5001 | + +```bash +git clone https://github.com/devonartis/agentwrit-python.git +cd agentwrit-python + +# Both demos drive an LLM to pick tools. Point at any OpenAI-compatible endpoint. +export LLM_API_KEY="sk-..." # or a local vLLM / llama.cpp server via LLM_BASE_URL + +docker compose up -d broker medassist # MedAssist → http://localhost:5000 +docker compose up -d broker support-tickets # Support Tickets → http://localhost:5001 +``` + +Each demo auto-registers itself with the broker on startup — no manual app setup. See [`demo/README.md`](https://github.com/devonartis/agentwrit-python/blob/main/demo/README.md) and [`demo2/README.md`](https://github.com/devonartis/agentwrit-python/blob/main/demo2/README.md) for scenario playbooks and code maps. ## MedAssist AI -A FastAPI web app where a local LLM dynamically creates broker agents with per-patient scoped credentials. You enter a patient ID and a plain-language request. The LLM chooses which tools to call, and the app creates agents with only the scopes those tools need — for that specific patient. +A FastAPI clinical assistant. You ask a plain-language question about a patient; a local LLM picks tools (records, labs, billing, prescriptions); the app spawns broker agents on demand, each scoped to *one patient and one category*. | What you'll see | What it proves | |---|---| | Agents spawn on demand per LLM tool call | Dynamic agent creation | -| Each agent scoped to one patient ID | Per-resource scope isolation | -| LLM asks for wrong patient's data | Scope enforcement catches cross-boundary access | +| Each agent scoped to one patient ID + category | Per-resource scope isolation | +| LLM asks for the wrong patient's data | Scope enforcement catches cross-boundary access | | Clinical agent delegates to prescription agent | Delegation with scope attenuation | | Tokens renew and release at end of encounter | Full lifecycle management | | Dedicated audit tab | Hash-chained broker events | ## Support Ticket Zero-Trust -Three LLM-driven agents process support tickets with broker-issued scoped credentials, streaming execution via SSE, and natural token expiry. - -## In the meantime +Flask + HTMX + SSE. Three LLM-driven agents (triage → knowledge → response) process customer tickets with broker-issued scoped credentials, streaming execution via SSE. -You can follow the [Quick Start](../README.md#quick-start) to run the broker and issue your first agent token in five minutes. The [Getting Started walkthrough](getting-started-user.md) covers the full registration flow with curl. +| What you'll see | What it proves | +|---|---| +| Anonymous tickets halt at triage | Identity gates the pipeline | +| `delete_account` / `send_external_email` in the LLM's tool list but not the agent's scope | Dangerous tools never execute — scope, not prompt, is the boundary | +| One scenario skips `release()` | A 5-second TTL dies on its own | -## Get notified +## Build your first agent without the demos -Watch this repo or [file an issue](https://github.com/devonartis/agentwrit/issues) to be notified when demos are available. +You can also run the broker and issue your first agent token directly. Follow the [Quick Start](../README.md#quick-start) or the [Getting Started walkthrough](getting-started-user.md) for the full curl-based registration flow. diff --git a/docs/design-decisions.md b/docs/design-decisions.md index 08b947f..7d80930 100644 --- a/docs/design-decisions.md +++ b/docs/design-decisions.md @@ -246,7 +246,7 @@ Specific mismatches: | Client credentials grant | Close to what apps do, but we add scope ceiling enforcement that OAuth doesn't have | | Token introspection | We have `POST /v1/token/validate`, but it's simpler — no active/inactive metadata, just valid/invalid + claims | -We're not anti-OAuth. Enterprise modules could bridge to OAuth/OIDC (that's what the planned OIDC provider add-on is for). But the core doesn't need OAuth's complexity. +We're not anti-OAuth. A planned OIDC bridge would let an AgentWrit identity token be exchanged for an OAuth/OIDC token that downstream services already trust — mounted behind a clean interface seam so the credential core stays simple. But the core itself doesn't need OAuth's complexity. --- @@ -314,7 +314,7 @@ Each decision builds on the previous: 6. **Tokens need permissions** → use `action:resource:identifier` scopes (granular, deterministic attenuation) 7. **System needs management layers** → three roles: admin (operator), app (autonomous within ceiling), agent (does work) 8. **Deployment can't be coupled to Docker** → remove sidecar, direct broker architecture -9. **OAuth doesn't fit machine-to-machine ephemeral agents** → own token system, OIDC bridge as enterprise add-on +9. **OAuth doesn't fit machine-to-machine ephemeral agents** → own token system, with a planned OIDC bridge behind an interface seam 10. **Admin launch token creation bypasses ceiling** → restrict to dev mode (TD-013) 11. **Different failure scenarios need different blast radii** → four revocation levels diff --git a/docs/faq.md b/docs/faq.md index 9aa1651..656c943 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -10,7 +10,7 @@ Questions from real practitioners evaluating AgentWrit for their environments. I AgentWrit handles the **identity plane** — proving who the agent is, what scope it has, and how long it can operate. It does not handle the **data plane** — injecting the actual secret (OAuth refresh token, database password, API key) for the downstream service. -This is by design. The credential exchange bridge — where an AgentWrit identity token gets swapped for a real service credential via Vault, AWS Secrets Manager, cloud federation, or OIDC token exchange — is an enterprise module that plugs into the core via interfaces. Keeping secret injection out of the open core means the core has zero access to your actual secrets. +This is by design. The credential exchange bridge — where an AgentWrit identity token gets swapped for a real service credential via Vault, AWS Secrets Manager, cloud federation, or OIDC token exchange — is a planned capability of the product, built behind a clean interface seam. Keeping secret injection out of the broker means the broker has zero access to your actual secrets — a security boundary, not a packaging one. **Who needs the bridge:** Anyone running agents against real APIs (Google Calendar, AWS services, databases) in production. @@ -26,9 +26,9 @@ This is by design. The credential exchange bridge — where an AgentWrit identit ### Will AgentWrit support OIDC token exchange? -OIDC provider and token exchange functionality is planned as an enterprise module. It would allow an AgentWrit identity token to be exchanged for an OIDC-compliant token that downstream services already trust — bridging AgentWrit's agent identity into existing OAuth2/OIDC infrastructure without those services needing to know about AgentWrit directly. +OIDC provider and token exchange functionality is planned for the product. It would allow an AgentWrit identity token to be exchanged for an OIDC-compliant token that downstream services already trust — bridging AgentWrit's agent identity into existing OAuth2/OIDC infrastructure without those services needing to know about AgentWrit directly. -The core broker exposes interfaces for this. The enterprise module plugs in without modifying core code. If you need this today, the source is available — you can build an OIDC bridge for your own internal use under the license. +The broker exposes a clean interface seam for this so the capability mounts without disturbing the credential core. If you need this today, the source is available — you can build an OIDC bridge for your own internal use under the license. --- @@ -55,9 +55,9 @@ Scopes only go in one direction: down. Every boundary narrows. No one in the cha ### Is there scope drift detection — granted vs actually-used? -Not yet. AgentWrit audits everything it *issues* — 24 event types in a tamper-evident hash chain. But it can't see what the agent *does* with the token at the resource server. A scope-usage audit that compares granted vs actually-used scopes would require resource servers to report back to the broker. +Not yet. AgentWrit audits everything it *issues* — 25 event types in a tamper-evident hash chain. But it can't see what the agent *does* with the token at the resource server. A scope-usage audit that compares granted vs actually-used scopes would require resource servers to report back to the broker. -This is a real gap and a great feature idea. The audit infrastructure is already in place — the question is whether to build it into core or as an enterprise integration that includes resource server reporting. Either way, the "silent 403 for days because the agent had readonly when it needed write" scenario is exactly what it would catch. +This is a real gap and a great feature idea. The audit infrastructure is already in place — the open question is the resource-server reporting half. Either way, the "silent 403 for days because the agent had readonly when it needed write" scenario is exactly what it would catch. **Related docs:** - [Architecture — audit trail](architecture.md#key-design-decisions) — how the hash-chain audit works today @@ -69,11 +69,12 @@ This is a real gap and a great feature idea. The audit infrastructure is already ### Where are the SDKs? The broker works but custom HTTP is an adoption barrier. -The Python SDK is cleaned, tested, and publishing soon. TypeScript SDK is next. Until then, every integration is custom HTTP against the [API reference](api.md). +The Python SDK is **live** — [`agentwrit`](https://pypi.org/project/agentwrit/) v0.3.0 on PyPI (`pip install agentwrit` or `uv add agentwrit`), MIT-licensed, source at [`devonartis/agentwrit-python`](https://github.com/devonartis/agentwrit-python). TypeScript SDK is next. For other languages it's custom HTTP against the [API reference](api.md). -The API is clean — 19 endpoints, JSON in/out, RFC 7807 errors — but we know "a day to integrate" is a real barrier. SDKs are the next priority. +The API is clean — JSON in/out, RFC 7807 errors — and the Python SDK takes most integrations to a handful of lines. See the [Python SDK guide](python-sdk.md). **Related docs:** +- [Python SDK](python-sdk.md) — the live client, install and API surface - [API Reference](api.md) — every endpoint, request/response shapes, error codes, rate limits - [Getting Started: Developer](getting-started-developer.md) — building an agent that authenticates with AgentWrit @@ -101,10 +102,10 @@ So agents with valid tokens keep working during a broker restart. They just can' ### Where's the end-to-end example? Show an agent getting a credential, calling a real API, credential expiring. -Coming. MedAssist is referenced in the docs but not shipped yet. An end-to-end demo — agent authenticates, gets scoped credential, calls a real API, credential expires — is on the roadmap. This is the thing that makes it real for people evaluating it. +Shipped. Two end-to-end demos run via Docker Compose against a live broker — MedAssist (healthcare) and Support Tickets (three-agent pipeline). An LLM picks tools, the app spawns scoped agents on demand, and you watch scope denials, delegation, and TTL expiry on real traffic. -In the meantime, the scenarios doc walks through the full flow with code: -- [Real-World Scenarios](scenarios.md) — the 8 components in production contexts +- [Demos](demos.md) — run steps, published images, what each one proves +- [Real-World Scenarios](scenarios.md) — the components in production contexts --- @@ -118,7 +119,7 @@ Yes. The PolyForm Internal Use License 1.0.0 means: - **Free for nonprofits, education, and research** — email `licensing@agentwrit.com` and it's done. - **Not free to resell or host as a SaaS** — building a managed service on top of AgentWrit requires a commercial license. -### Can I build the enterprise features myself? +### Can I build the unbuilt features myself? Yes — for your own internal use. The source code is public. You can build your own Vault bridge, OIDC exchange, scope telemetry, or anything else. Modify the code, extend it, deploy it internally. The only restriction is you can't resell it or offer it as a hosted service to others. @@ -130,7 +131,7 @@ The [EAC v1.3 pattern](https://github.com/devonartis/AI-Security-Blueprints/blob ### "The identity layer is done. The last mile is not." -Fair summary. The identity plane is solid — challenge-response registration, SPIFFE identities, scope attenuation, 4-level revocation, tamper-evident audit, delegation chains. The bridge to real service credentials is the enterprise module. The SDKs and demo are in progress. +Fair summary. The identity plane is solid — challenge-response registration, SPIFFE identities, scope attenuation, 4-level revocation, tamper-evident audit, delegation chains. The Python SDK and both demos have shipped. The bridge to real service credentials is the next planned capability. AgentWrit was purpose-built for agents — not retrofitted from human IAM. Traditional IAM was designed for humans and long-running services. Agents are ephemeral, task-scoped, and delegate to each other. That's a different lifecycle, and it needs a different credential system. diff --git a/docs/foundations.md b/docs/foundations.md index e75ffc4..6bd431e 100644 --- a/docs/foundations.md +++ b/docs/foundations.md @@ -131,9 +131,9 @@ header.payload.signature **Payload** — the claims (who you are, what you can do, when it expires): ```json { - "iss": "agentwrit", + "iss": "https://broker.example.com", "sub": "spiffe://agentwrit.local/agent/orch-456/task-789/a1b2c3d4", - "aud": ["agentwrit"], + "aud": ["https://broker.example.com"], "exp": 1711810500, "nbf": 1711810200, "iat": 1711810200, @@ -149,7 +149,7 @@ header.payload.signature | Claim | What it carries | |-------|----------------| -| `iss` | Issuer — always `"agentwrit"`. Tokens claiming a different issuer are rejected | +| `iss` | Issuer — the broker's identity, set by the operator via `AA_ISSUER`. Empty by default; when set, tokens with a mismatched `iss` are rejected, and when empty the issuer check is skipped (mirrors the audience contract) | | `sub` | Subject — who this token represents. `"admin"` for operators, `"app:{id}"` for apps, SPIFFE URI for agents | | `aud` | Audience — who this token is intended for. Checked if configured | | `exp` | Expiration — Unix timestamp. Token is dead after this | diff --git a/docs/python-sdk.md b/docs/python-sdk.md index 0acc79f..a65613d 100644 --- a/docs/python-sdk.md +++ b/docs/python-sdk.md @@ -1,38 +1,60 @@ # AgentWrit Python SDK -> **Coming soon to public.** The Python SDK is complete and will be published at [`devonartis/agentwrit-python`](https://github.com/devonartis/agentwrit-python) after final cleanup. +The Python client for the broker is live: **[`agentwrit`](https://pypi.org/project/agentwrit/) v0.3.0** on PyPI (MIT, Python 3.10+), source at [`devonartis/agentwrit-python`](https://github.com/devonartis/agentwrit-python). It wraps the broker's Ed25519 challenge-response registration flow into simple calls — you don't manage nonces, signatures, or token renewal manually. -## What it does +```bash +uv add agentwrit # or: pip install agentwrit +``` + +The SDK pulls in `httpx` and `cryptography` automatically. -The Python SDK wraps the broker's Ed25519 challenge-response registration flow into simple function calls. You don't need to manage nonces, signatures, or token renewal manually. +## Five lines ```python -from agentwrit import AgentWritApp +from agentwrit import AgentWritApp, validate + +# App authenticates with its client_id/client_secret (issued by the broker operator). +app = AgentWritApp(broker_url, client_id, client_secret) + +# Create an ephemeral agent scoped to one task. +agent = app.create_agent("my-service", "task-1", ["read:data:customer-7291"]) -agent = AgentWritApp(broker_url="http://localhost:8080").register( - launch_token=LAUNCH_TOKEN, - task_id="read-customer-42", - requested_scope=["read:data:customers:42"], -) +# Use the token at any resource server. +httpx.get("https://api/customers/7291", headers=agent.bearer_header) -# Use the token at your resource server -response = httpx.get(url, headers=agent.bearer_header) +# Any service can verify the token against the broker. +validate(app.broker_url, agent.access_token) -# Done — release the credential +# Done — the token dies at the broker. agent.release() ``` +## API surface + +**`AgentWritApp(broker_url, client_id, client_secret)`** — the developer's entry point. One app, many agents. + +- `create_agent(orch_id, task_id, requested_scope, *, private_key=None, max_ttl=300, label=None)` → `Agent` + +**`Agent`** — an ephemeral, per-task principal. Its authority can only narrow from the app's ceiling. + +- `bearer_header` → `{"Authorization": "Bearer …"}` for resource-server calls +- `access_token` → the raw JWT +- `renew()` — extend the token; the old one is immediately revoked +- `release()` — revoke the token at the broker +- `delegate(...)` — derive a further-attenuated token for a sub-task + +**Module helpers** + +- `validate(broker_url, token)` → `ValidateResult` — any service can verify a token +- `scope_is_subset(requested, allowed)` → `bool` — check attenuation before asking + ## Current status - **v0.3.0** — 15 acceptance tests passing against a live broker - Full agent lifecycle: register, renew, delegate, release -- Scope checking and validation helpers -- `pip install agentwrit` +- Structured RFC 7807 error exceptions (`AuthorizationError`, `TransportError`, …) +- **Synchronous** — on FastAPI/Starlette/Sanic, wrap calls in `asyncio.to_thread(...)` -## In the meantime +## Without the SDK You can use the broker's HTTP API directly from any language. See the [Getting Started walkthrough](getting-started-user.md) for the full curl-based registration flow, or the [API Reference](api.md) for all endpoints. - -## Get notified - -Watch this repo or [file an issue](https://github.com/devonartis/agentwrit/issues) to be notified when the SDK goes public. From aad38cdf97e766355db4fa6073f15c9e66b7e64f Mon Sep 17 00:00:00 2001 From: Divine Date: Mon, 29 Jun 2026 18:25:03 -0400 Subject: [PATCH 2/3] fix(ci): bump Go toolchain go1.25.10 -> go1.25.11 Clears two Go stdlib advisories flagged by govulncheck: - GO-2026-5039 (net/textproto), reached via ed25519.GenerateKey - GO-2026-5037 (crypto/x509) Both fixed in go1.25.11. CI reads go-version-file: go.mod, so the toolchain directive bump applies across all jobs. Comment refs in ci.yml updated to match. Co-authored-by: Claude --- .github/workflows/ci.yml | 4 ++-- go.mod | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 790ba38..a443424 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,10 +66,10 @@ jobs: # DO NOT use golangci/golangci-lint-action here. Its pre-built # binaries are compiled against an older Go toolchain (≤ 1.23) # and exit 3 on our code because the go.mod toolchain directive - # is 1.25.10 — the embedded linter can't parse 1.25 stdlib/SSA. + # is 1.25.11 — the embedded linter can't parse 1.25 stdlib/SSA. # # Instead, `go install` golangci-lint from source. This compiles - # it with the CI runner's Go (matching our toolchain.go1.25.10) + # it with the CI runner's Go (matching our toolchain.go1.25.11) # so it parses the same way as local developers' brew-installed # binary. Migration to golangci-lint v2 (which fixes this) is # tracked for a later cycle. diff --git a/go.mod b/go.mod index 3f25d39..c7066a5 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/devonartis/agentwrit go 1.24.0 -toolchain go1.25.10 +toolchain go1.25.11 require ( github.com/prometheus/client_golang v1.23.2 From 9b8fa84b32830a3d495c04d51023965b84b6b5ac Mon Sep 17 00:00:00 2001 From: Divine Date: Mon, 29 Jun 2026 18:43:52 -0400 Subject: [PATCH 3/3] docs: fix MED/LOW tier doc-accuracy audit findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second pass over the 2026-06-07 audit (MED/LOW + cross-cutting), all verified against current code: - README.md: add missing POST /v1/app/launch-tokens row (19 endpoints) - awrit-reference.md: config file is KEY=VALUE not YAML (MODE=/ADMIN_SECRET=); audit IDs are evt-NNNNNN not UUID; app deregister status is 'inactive' - troubleshooting.md: go run -> ./bin/; /v1/register has no requested_ttl (TTL comes from launch-token max_ttl); config path is /etc/broker/config - concepts.md: hash covers 13 fields; claims include iss/nbf/aud/sid; eight components (was 'seven') - security-topology.md + 2 SVG diagrams: 24 -> 25 event types - architecture.md: prometheus/client_model marked indirect (5 direct deps) - implementation-map.md + scenarios.md: mutauth built+tested but NOT mounted - getting-started-{user,operator}.md: go run -> ./bin/ - CHANGELOG.md: [Unreleased] docs-accuracy + Go toolchain entries Deferred: RFC 7807-vs-9457 reconciliation (code emits 7807, rules say 9457 — owner decision, out of docs-only scope). AGENTS.md/CONTRIBUTING.md fixes are local-only (both gitignored, not tracked in this repo). Co-authored-by: Claude --- CHANGELOG.md | 10 ++++++++ README.md | 1 + docs/architecture.md | 2 +- docs/awrit-reference.md | 28 ++++++++++---------- docs/concepts.md | 6 ++--- docs/diagrams/architecture-overview.svg | 2 +- docs/diagrams/security-topology.svg | 2 +- docs/getting-started-operator.md | 7 ++--- docs/getting-started-user.md | 2 +- docs/implementation-map.md | 2 +- docs/scenarios.md | 2 +- docs/security-topology.md | 2 +- docs/troubleshooting.md | 34 ++++++++++--------------- 13 files changed, 54 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf1d2f2..a6c954b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Documentation — accuracy audit fixes (2026-06-29) + +- Corrected shipped docs against current code and the live Python SDK. The Python SDK ([`agentwrit`](https://pypi.org/project/agentwrit/) v0.3.0) and both demo apps (MedAssist, Support Tickets — published Docker images) are **live**; removed all "coming soon" banners and a fabricated SDK API from `docs/demos.md` and `docs/python-sdk.md`. +- Dropped stale "enterprise module / open-core" framing (Decision 022 — one product, license is the moat) from `docs/faq.md` and `docs/design-decisions.md`. +- Fixed user-facing accuracy bugs: sanitized token-validate error shape, config-driven `iss` claim (`AA_ISSUER`), `/v1/app/auth` rate limit (10/min, burst 3), audit event count (25), `KEY=VALUE` config-file format, `evt-NNNNNN` audit IDs, app deregister status (`inactive`), `go run` → `./bin/` per project rules, and OpenAPI schema fields. Added GitHub Security Advisory as the preferred disclosure channel in `SECURITY.md`. + +### Security — Go toolchain bump (2026-06-29) + +- Bumped `go.mod` toolchain `go1.25.10` → `go1.25.11`, clearing two Go stdlib advisories flagged by govulncheck: `GO-2026-5039` (net/textproto) and `GO-2026-5037` (crypto/x509). CI reads `go-version-file: go.mod`, so the bump applies across all jobs. + ### Security — OSSF Scorecard Tier-1 hardening (2026-05-13) - Moved top-level write permissions to job level in `.github/workflows/codeql.yml` (`security-events: write`, `actions: read`) and `.github/workflows/release.yml` (`packages: write`, `id-token: write`). Top level keeps `contents: read` only. OSSF Scorecard Token-Permissions check expected to lift from 0 → 10. diff --git a/README.md b/README.md index a15ce2b..df13eae 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,7 @@ agent.release() | | `POST` | `/v1/token/validate` | Resource server — verify a token | | **Auth** | `POST` | `/v1/admin/auth` | Operator — get admin JWT | | | `POST` | `/v1/app/auth` | App — client credential exchange | +| | `POST` | `/v1/app/launch-tokens` | App — create agent launch tokens within its ceiling | | | `POST` | `/v1/register` | Agent — register with launch token | | **Token** | `POST` | `/v1/token/renew` | Agent/App — renew before expiry | | | `POST` | `/v1/token/release` | Agent — signal task completion | diff --git a/docs/architecture.md b/docs/architecture.md index 4d59d27..0786d0b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -333,7 +333,7 @@ These are explicit trust boundaries and limitations of the current implementatio | Dependency | Version | Purpose | |---|---|---| | `github.com/prometheus/client_golang` | v1.23.2 | Metrics exposition | -| `github.com/prometheus/client_model` | v0.6.2 | Metrics data model | +| `github.com/prometheus/client_model` | v0.6.2 | Metrics data model (indirect — pulled in by `client_golang`, not a direct dependency) | | `github.com/spiffe/go-spiffe/v2` | v2.6.0 | SPIFFE ID validation | | `github.com/spf13/cobra` | v1.10.2 | CLI framework for `awrit` | | `modernc.org/sqlite` | v1.46.1 | Pure-Go SQLite driver for audit event persistence (zero CGo) | diff --git a/docs/awrit-reference.md b/docs/awrit-reference.md index 115f32f..c0bc03f 100644 --- a/docs/awrit-reference.md +++ b/docs/awrit-reference.md @@ -115,7 +115,7 @@ Query the broker's audit trail. Events record all meaningful actions: token issu | Column | Description | |--------|-------------| -| ID | Unique event identifier (UUID) | +| ID | Unique event identifier (sequential, e.g. `evt-000001`) | | TIMESTAMP | RFC3339 timestamp when the event occurred | | EVENT TYPE | Type of event (token_issued, token_revoked, etc.) | | AGENT ID | SPIFFE ID of the agent involved (or empty for broker operations) | @@ -128,7 +128,7 @@ Query the broker's audit trail. Events record all meaningful actions: token issu { "events": [ { - "id": "550e8400-e29b-41d4-a716-446655440000", + "id": "evt-000001", "timestamp": "2026-02-27T15:32:10Z", "event_type": "token_issued", "agent_id": "spiffe://example.com/agent/crawler", @@ -634,7 +634,7 @@ Existing agent tokens issued by this app remain valid until expiration; use `awr | Column | Description | |--------|-------------| | APP_ID | App identifier | -| STATUS | Status after deregistration (deregistered) | +| STATUS | Status after deregistration (`inactive` — soft delete, record retained) | | DEREGISTERED_AT | Deregistration timestamp | **Output (JSON):** @@ -642,7 +642,7 @@ Existing agent tokens issued by this app remain valid until expiration; use `awr ```json { "app_id": "app-12345", - "status": "deregistered", + "status": "inactive", "deregistered_at": "2026-02-27T15:32:10Z" } ``` @@ -723,18 +723,20 @@ awrit init --force **Output File Format:** -The config file is YAML containing the admin secret (plaintext in dev, bcrypt hash in prod): +The config file is a plain `KEY=VALUE` file (not YAML) containing the admin secret (plaintext in dev, bcrypt hash in prod). Lines starting with `#` are comments; the parser reads `MODE` and `ADMIN_SECRET`: -```yaml -mode: development -admin_secret: dGVzdC1zZWNyZXQtdmFsdWU= +```ini +# Broker Configuration +# Generated by: awrit init --mode=dev +MODE=development +ADMIN_SECRET=dGVzdC1zZWNyZXQtdmFsdWU= ``` or in prod: -```yaml -mode: production -admin_secret: $2a$12$...bcrypt.hash.here... +```ini +MODE=production +ADMIN_SECRET=$2a$12$...bcrypt.hash.here... ``` --- @@ -869,7 +871,7 @@ awrit audit events Output: ``` ID TIMESTAMP EVENT TYPE AGENT ID OUTCOME DETAIL -550e8400-e29b-41d4-a716-446655440000 2026-02-27T15:32:10Z token_issued spiffe://example.com/agent/crawler success Token issued for scope read +evt-000001 2026-02-27T15:32:10Z token_issued spiffe://example.com/agent/crawler success Token issued for scope read ``` **Advantages:** @@ -894,7 +896,7 @@ Output: { "events": [ { - "id": "550e8400-e29b-41d4-a716-446655440000", + "id": "evt-000001", "timestamp": "2026-02-27T15:32:10Z", "event_type": "token_issued", "agent_id": "spiffe://example.com/agent/crawler", diff --git a/docs/concepts.md b/docs/concepts.md index 49da9ee..cec5748 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -68,7 +68,7 @@ AgentWrit implements the **Ephemeral Agent Credentialing** pattern, a security a 5. **Immutable Accountability** -- All agent actions are logged in tamper-proof audit trails 6. **Delegation Chain Integrity** -- Multi-agent workflows maintain cryptographic proof of authorization lineage -These principles manifest as **seven concrete components** that work together to eliminate the credential exposure problem: +These principles manifest as **eight concrete components** that work together to eliminate the credential exposure problem: ```mermaid flowchart TB @@ -172,7 +172,7 @@ stateDiagram-v2 ``` **What AgentWrit implements:** -- JWTs signed with EdDSA (Ed25519), containing claims: `sub` (SPIFFE ID), `scope`, `task_id`, `orch_id`, `delegation_chain`, `chain_hash`, `jti`, `exp`, `iat` +- JWTs signed with EdDSA (Ed25519), containing claims: `iss`, `sub` (SPIFFE ID), `aud`, `scope`, `task_id`, `orch_id`, `delegation_chain`, `chain_hash`, `jti`, `sid`, `exp`, `nbf`, `iat` - Scope format: `action:resource:identifier` with wildcard `*` support in the identifier position - Default TTL of 300 seconds (5 minutes), configurable via `AA_DEFAULT_TTL` - Scope attenuation enforced at registration, delegation, and app-authenticated launch token creation @@ -290,7 +290,7 @@ flowchart LR E2 -->|"hash feeds into"| E3 ``` -Each hash is computed over: `prev_hash | event_id | timestamp | event_type | agent_id | task_id | orch_id | detail`. The genesis event uses 64 zeros as its previous hash. If an attacker modifies any event, every subsequent hash becomes invalid. +Each hash is computed over 13 fields: `prev_hash | event_id | timestamp | event_type | agent_id | task_id | orch_id | detail | resource | outcome | deleg_depth | deleg_chain_hash | bytes_transferred`. The genesis event uses 64 zeros as its previous hash. If an attacker modifies any event, every subsequent hash becomes invalid. **What AgentWrit implements:** - `AuditLog` provides append-only storage with automatic hash chaining using SHA-256 diff --git a/docs/diagrams/architecture-overview.svg b/docs/diagrams/architecture-overview.svg index 85299b7..bde49d0 100644 --- a/docs/diagrams/architecture-overview.svg +++ b/docs/diagrams/architecture-overview.svg @@ -201,7 +201,7 @@ Audit Log SHA-256 hash chain — each entry hashes the previous - 24 event types (auth, register, issue, revoke, delegate...) + 25 event types (auth, register, issue, revoke, delegate...) Tamper-evident · Queryable by operator · Persistent diff --git a/docs/diagrams/security-topology.svg b/docs/diagrams/security-topology.svg index 066b901..ebe4ad6 100644 --- a/docs/diagrams/security-topology.svg +++ b/docs/diagrams/security-topology.svg @@ -181,7 +181,7 @@ Hash-Chain Audit Tamper-evident trail - 24 event types logged + 25 event types logged Persistent to SQLite Queryable by operator diff --git a/docs/getting-started-operator.md b/docs/getting-started-operator.md index 3d719b5..a518b6f 100644 --- a/docs/getting-started-operator.md +++ b/docs/getting-started-operator.md @@ -242,7 +242,7 @@ export AA_TLS_CERT=/etc/broker/certs/broker.crt export AA_TLS_KEY=/etc/broker/certs/broker.key export AA_ADMIN_SECRET="$(openssl rand -hex 32)" -go run ./cmd/broker +go build -o bin/broker ./cmd/broker && ./bin/broker # AgentWrit broker v2.0.0 listening on :8080 (TLS) ``` @@ -263,7 +263,7 @@ export AA_TLS_KEY=/etc/broker/certs/broker.key export AA_TLS_CLIENT_CA=/etc/broker/certs/client-ca.crt export AA_ADMIN_SECRET="$(openssl rand -hex 32)" -go run ./cmd/broker +go build -o bin/broker ./cmd/broker && ./bin/broker ``` Clients must present a certificate signed by the configured client CA: @@ -354,7 +354,8 @@ Set `AA_DB_PATH` to a stable location on the host: ```bash export AA_DB_PATH="/var/lib/broker/data.db" -AA_ADMIN_SECRET="..." go run ./cmd/broker +go build -o bin/broker ./cmd/broker +AA_ADMIN_SECRET="..." ./bin/broker ``` In Docker Compose, mount a volume so the database survives container replacement: diff --git a/docs/getting-started-user.md b/docs/getting-started-user.md index a775aca..a897ecb 100644 --- a/docs/getting-started-user.md +++ b/docs/getting-started-user.md @@ -99,7 +99,7 @@ go build ./... # 3. Start the broker export AA_ADMIN_SECRET="$(openssl rand -hex 32)" -go run ./cmd/broker +go build -o bin/broker ./cmd/broker && ./bin/broker ``` The broker will log to stdout. You should see a "broker started" message. diff --git a/docs/implementation-map.md b/docs/implementation-map.md index ed408c2..617c054 100644 --- a/docs/implementation-map.md +++ b/docs/implementation-map.md @@ -311,7 +311,7 @@ Step 3 — Complete: ### Status -Implemented as a Go API. Not HTTP-exposed. Intended for future HTTP endpoint registration. Fully tested in unit tests. +Built as a Go API and covered by unit tests, but **not wired into the running broker** — `cmd/broker` does not mount these handlers, so mutual auth is not active in a live deployment. Not HTTP-exposed; mounting it (heartbeat + handshake + discovery) is planned future work. --- diff --git a/docs/scenarios.md b/docs/scenarios.md index d635bb2..4d66591 100644 --- a/docs/scenarios.md +++ b/docs/scenarios.md @@ -599,7 +599,7 @@ requests.post(f"{BROKER}/v1/revoke", | 7. Delegation | — | Scope narrowing | — | — | 3-level: central→regional→branch | | 8. Observability | Metrics per scope | Revocation alerts | SLA monitoring | Batch completion metrics | Agent count + depth alerts | -Component 6 (Mutual Auth) is implemented as a Go API and applies when agents need to verify each other's identity directly — for example, bank branch agents in a mesh topology exchanging compliance data (Scenario 5). +Component 6 (Mutual Auth) is built as a Go API (unit-tested, not HTTP-exposed and not yet mounted in the broker) and would apply when agents need to verify each other's identity directly — for example, bank branch agents in a mesh topology exchanging compliance data (Scenario 5). --- diff --git a/docs/security-topology.md b/docs/security-topology.md index 2091017..bd454c8 100644 --- a/docs/security-topology.md +++ b/docs/security-topology.md @@ -26,7 +26,7 @@ Scopes only move in one direction: down. Every boundary is enforced at issuance ## Security properties - **Challenge-response** — Ed25519 keypair per agent instance. No shared secrets at the agent level. -- **Hash-chain audit** — tamper-evident trail with 24 event types. Each record hashes the previous. +- **Hash-chain audit** — tamper-evident trail with 25 event types. Each record hashes the previous. - **Scope attenuation** — scopes cannot widen; equal or narrower is accepted. Delegation preserves the original principal. - **Token TTL** — default 5 minutes, max 24 hours (configurable). Per-app override available. Revocable at 4 levels. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 80465a2..b60c934 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -365,7 +365,7 @@ The broker process exits immediately with exit code 1. ```bash export AA_ADMIN_SECRET="$(openssl rand -hex 32)" -go run ./cmd/broker +go build -o bin/broker ./cmd/broker && ./bin/broker ``` For Docker Compose: @@ -416,15 +416,15 @@ Or: ```bash awrit init --mode dev # or -awrit init --mode prod --config-path /path/to/config.yaml +awrit init --mode prod --config-path /path/to/config ``` -This creates a configuration file with a bcrypt-hashed admin secret. Set `AA_CONFIG_PATH` to point to this file: +This creates a `KEY=VALUE` configuration file (not YAML — lines like `MODE=prod`, `ADMIN_SECRET=`) with a bcrypt-hashed admin secret. Set `AA_CONFIG_PATH` to point to this file: ```bash export AA_CONFIG_PATH="/path/to/config" unset AA_ADMIN_SECRET # Must be UNSET (not empty) to fall through to config file -go run ./cmd/broker +go build -o bin/broker ./cmd/broker && ./bin/broker ``` > **Warning:** Do not set `AA_ADMIN_SECRET=""` — an empty string is on the denylist and will cause the broker to exit. Leave the variable **unset** to let the config file provide the secret. @@ -508,25 +508,19 @@ Or token is issued but with a shorter `expires_in` than requested. ```bash export AA_MAX_TTL=604800 # 7 days -go run ./cmd/broker +go build -o bin/broker ./cmd/broker && ./bin/broker ``` 2. **If you want to disable the ceiling entirely:** ```bash export AA_MAX_TTL=0 # 0 disables the ceiling -go run ./cmd/broker +go build -o bin/broker ./cmd/broker && ./bin/broker ``` -3. **If you want shorter-lived tokens (recommended for security), request a TTL within the ceiling:** +3. **If you want shorter-lived tokens (recommended for security), set a smaller `max_ttl` on the launch token.** -```python -reg = requests.post(f"{BROKER}/v1/register", json={ - ... - "requested_ttl": 300, # 5 minutes, well within any ceiling - ... -}) -``` + The agent token's lifetime comes from the launch token's `max_ttl` (set by the operator/app when creating the launch token), clamped by `AA_MAX_TTL`. The `/v1/register` request has no TTL field — it only takes `launch_token`, `nonce`, `public_key`, `signature`, `orch_id`, `task_id`, and `requested_scope`. To cap a token at 5 minutes, create the launch token with `"max_ttl": 300`. 4. **For Docker Compose, add the env var to the broker service:** @@ -545,7 +539,7 @@ broker: Broker fails to start with one of these errors: ``` -FATAL: config file not found at path: /etc/broker/config.yaml +FATAL: config file not found at path: /etc/broker/config ``` ``` @@ -570,7 +564,7 @@ FATAL: admin secret rejected: does not meet security requirements awrit init --mode dev # Production mode (longer TTLs, bcrypt-hashed admin secret, config file) -awrit init --mode prod --config-path /etc/broker/config.yaml +awrit init --mode prod --config-path /etc/broker/config ``` 2. **For development, use environment variables instead of a config file:** @@ -579,22 +573,22 @@ awrit init --mode prod --config-path /etc/broker/config.yaml export AA_ADMIN_SECRET="test-secret-at-least-16-chars" export AA_DEFAULT_TTL=300 export AA_MAX_TTL=3600 -go run ./cmd/broker +go build -o bin/broker ./cmd/broker && ./bin/broker ``` 3. **Ensure the config file is readable by the broker process:** ```bash -ls -la /etc/broker/config.yaml +ls -la /etc/broker/config # Should have read permissions for the broker user ``` 4. **To use the config file with the broker:** ```bash -export AA_CONFIG_PATH=/etc/broker/config.yaml +export AA_CONFIG_PATH=/etc/broker/config # Leave AA_ADMIN_SECRET unset or it will override the config file -go run ./cmd/broker +go build -o bin/broker ./cmd/broker && ./bin/broker ``` 5. **Env vars override config file values.** If both `AA_CONFIG_PATH` and individual env vars are set, env vars take precedence.