From cc42b53c9733225a4a56a872564bb4906d3296e3 Mon Sep 17 00:00:00 2001 From: Alexander Ververis Date: Tue, 28 Jul 2026 03:19:36 +0700 Subject: [PATCH 1/5] Rewrite README for open source users --- Cargo.toml | 2 +- README.md | 270 +++++++++++++++++++++++++-------------- examples/charon.dev.toml | 35 +++++ mise.toml | 6 +- 4 files changed, 215 insertions(+), 98 deletions(-) create mode 100644 examples/charon.dev.toml diff --git a/Cargo.toml b/Cargo.toml index c5b2d06..3fd1b8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "charon" version = "0.1.0" edition = "2024" rust-version = "1.97" -description = "Persona-aware credential injection proxy for secretless workloads" +description = "Credential-injecting forward proxy for workloads" license = "Apache-2.0" repository = "https://github.com/ak5/charon" publish = false diff --git a/README.md b/README.md index 9aeebbd..3bb6b37 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,196 @@ # Charon -Charon is a persona-aware credential injection proxy for secretless workloads. -An untrusted developer container sends a public placeholder credential; Charon -authorizes the destination, resolves the real credential outside the container, -injects it into the upstream request, and emits a redacted audit event. +Charon is a forward proxy that adds credentials to approved outbound requests. +It lets a workload call an API without putting the API credential in that +workload's environment, filesystem, or container image. + +The workload sends a harmless placeholder instead of a real credential. Charon +checks a short-lived signed authorization, matches the request against local +policy, obtains the credential from the configured secret store, and replaces +the placeholder only in the request sent upstream. ```text -developer workload (no real secrets) - -> Charon (policy + credential injection) - -> configured egress (optional Squid) - -> GitHub / vendor API +workload Charon API +no stored credential ──▶ verify + apply policy ──▶ authenticated request +placeholder only resolve credential ``` -## Status - -Milestone 0 is an intentionally constrained proxy proof: - -- exact-host allowlisting; -- exact placeholder replacement; -- provider abstraction with environment and locked-by-default Vaultwarden - implementations; -- optional upstream HTTP proxy with protected file-backed authentication; -- backpressured request and response streaming with independent 16 MiB limits; -- HTTPS `CONNECT` interception with HTTP/2 and HTTP/1.1 ALPN; -- redirects disabled; -- JSON audit events without headers or bodies; -- fail-closed tests. - -HTTPS `CONNECT` interception, signed workload identity, and the Vaultwarden -provider are implemented but not yet selected by the milestone-0 deployment. -The owner CONNECT security review and disposable secretless `gh api user` -vertical proof are complete. See [`docs/threat-model.md`](docs/threat-model.md). - -## When Charon resolves a credential - -An HTTP request alone cannot select or retrieve a credential. Before resolution, -Charon verifies a signed, short-lived, single-use workload manifest and binds it -to the configured issuer, audience, realm tenant/persona, workspace, active -lease, workload, operation correlation, and named capability. It -then requires the policy's exact destination hostname, method, path, and public -placeholder. For HTTPS, the CONNECT hostname, TLS SNI, and decrypted request -authority must also agree. - -Remote credential-bearing requests require HTTPS on port 443. Plaintext HTTP -is accepted only for literal loopback addresses used by local fixtures. - -The capability persona maps to a caller-independent credential reference; the -Vaultwarden provider maps that reference and persona to one exact configured -item UUID. After every check passes, Charon replaces the public placeholder only -in the outbound request to the destination. It never returns the resolved value -to the workload, and it never follows redirects with an injected credential. - -The Vaultwarden account is itself a trust boundary: production runs one isolated -Charon realm and least-privilege account per persona. Every mapping and -capability must match the realm's declared persona. Do not connect Charon to a -personal, broadly privileged, or cross-persona vault. See -the complete invariants and residual risks in -[`docs/threat-model.md`](docs/threat-model.md). +Charon is an early-stage project. Its core proxy, authorization, HTTPS +interception, provider adapter, and tests are implemented. The deployment and +integration contracts are still being refined before a production release. + +## Why use it? + +Tools often need credentials for services such as GitHub, package registries, +or internal APIs. Giving every tool a long-lived token makes that token +available to the tool and to anything that compromises it. + +Charon moves the credential into a smaller, separately operated process. A +request is allowed only when all of these agree: + +- a signed, short-lived, single-use workload manifest; +- a named capability in Charon's configuration; and +- the actual destination hostname, HTTP method, and path. + +The workload cannot choose a secret, a secret-store item, or an unconfigured +destination. Charon does not return credentials to workloads and does not +follow redirects after adding one. + +## Concepts + +These names appear in the configuration and protocol: + +| Term | Meaning | +| --- | --- | +| **Workload** | The program making the outbound request, such as a CLI, agent, build, or development container. | +| **Manifest** | A short-lived, signed authorization carried with one request. It identifies the workload and names one capability. Each manifest can be used once. | +| **Capability** | A named permission in Charon's local policy, for example “read the current GitHub user.” It maps to one service and an exact set of methods and paths. | +| **Service** | A configured destination and credential-injection rule: exact hostnames, the credential header, its placeholder, and a secret reference. | +| **Secret provider** | The adapter Charon uses to obtain a credential. The current implementations are an environment provider for disposable development and a Vaultwarden provider. | +| **Realm** | One isolated Charon instance and its configuration, provider session, and policy. A realm serves one tenant and one persona. | +| **Tenant** | The organization or administrative owner of a realm. | +| **Persona** | The stable human or automation identity whose credentials the realm may use, such as `alice`, `release-bot`, or `github-readonly`. It is a credential-isolation label, not a role-playing concept. | +| **Workspace** | The project or working environment that received the manifest. | +| **Lease** | The current authorized lifetime or assignment of that workspace. Replacing the lease invalidates authorizations tied to the old assignment. | + +Tenant, persona, workspace, and lease are asserted by the manifest issuer and +checked against the realm. They give an integrating system enough identity +context to distinguish, for example, Alice's project from a release bot without +letting either select credentials directly. + +## How a request works + +1. A trusted issuer gives the workload a signed manifest for a named + capability. +2. The workload sends a normal proxy request to Charon with + `Proxy-Authorization: Charon ` and the configured public + placeholder in the credential header. +3. Charon verifies the signature, expiry, realm identity, and single-use nonce. +4. Charon resolves the capability from its own configuration and checks the + request's exact host, method, and path. +5. Charon asks its configured provider for the policy-owned secret reference. +6. Charon replaces the placeholder in the upstream request and returns the + API's response. + +For HTTPS, the workload connects through Charon using HTTP `CONNECT` and trusts +the operator-provided Charon CA. The +[forward-proxy contract](contracts/forward-proxy.md) specifies the complete wire +protocol. Charon's ordinary health endpoints are described by +[OpenAPI](contracts/openapi.yaml). ## Development -[mise](https://mise.jdx.dev/) pins Rust 1.97.1. Rust 2024 implies Cargo's -Rust-version-aware resolver, and `rust-version` documents the supported compiler. +The project requires Rust 1.97 or newer. [mise](https://mise.jdx.dev/) is +optional; it installs the pinned toolchain and provides short names for common +development commands. ```sh mise install mise run check ``` -CI uses GitHub-hosted Linux runners so forks work without repository -configuration. Dependabot targets `dev`; release pull requests alone flow from -`dev` to protected `main`. +`mise run ` means “run a task defined in `mise.toml`.” For example, +`mise run dev` runs the task named `dev`: + +```sh +mise run dev +``` + +That command is equivalent to: + +```sh +cargo run -- --config examples/charon.dev.toml +``` + +The development configuration listens only on `127.0.0.1:3129`, uses the +environment provider, and contains no real credential. It is suitable for +starting the process and inspecting its health endpoints; exercising an +authenticated proxy request also requires issuing a valid test manifest. -Run the development service with a disposable test token in Charon's process: +Run the individual checks directly if you do not use mise: ```sh -export CHARON_GITHUB_TOKEN=test-only -mise run run +cargo fmt --all -- --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-targets --all-features +cargo deny check ``` -Use real credentials only in the reviewed disposable vertical fixture. The -milestone-0 deployment remains limited to its disposable environment credential -until the documented Vaultwarden cutover is provisioned. - -The immutable non-production release, verification, rollback, and removal -contract is documented in [`docs/deployment.md`](docs/deployment.md). -The internal-network, secretless `gh api user` integration fixture is documented -in [`integration/vertical/README.md`](integration/vertical/README.md). - -## Integration ownership - -- This repository owns the generic Rust binary, container image, policy format, - tests, and security documentation. -- The operator owns listeners, network policy, secret-store connectivity, - backups, deployment, and the external realm reconciler. -- The integrating control plane owns tenant/persona/workspace lifecycle and - issues short-lived signed workload manifests. -- Charon never calls either system on its request path. - -The integration boundaries are indexed in -[`docs/integration-boundaries.md`](docs/integration-boundaries.md). -Machine-readable schemas and the normative -[`forward-proxy protocol`](contracts/forward-proxy.md) live in -[`contracts/`](contracts/). The production persona-realm boundary and external -reconciler contract are in -[`ADR 0003`](docs/adr/0003-persona-realms.md) and -[`docs/persona-realm-contract.md`](docs/persona-realm-contract.md). -The secret-store extension contract and its fail-closed constraints are in -[`ADR 0004`](docs/adr/0004-secret-provider-adapters.md). -The optional external human-approval broker and channel adapter are specified -by [`ADR 0005`](docs/adr/0005-human-approval-broker.md) and the -[`approval contracts`](contracts/README.md); Charon has no Telegram dependency -or online approval lookup. +## Configuration + +[`examples/charon.dev.toml`](examples/charon.dev.toml) is a minimal local +configuration. [`examples/charon.toml`](examples/charon.toml) shows the +Vaultwarden, TLS, identity, capability, and service settings used in an +operator-managed deployment. + +Configuration is deny-by-default: + +- destination hosts are exact names; wildcards are not supported; +- capabilities list exact HTTP methods and paths; +- remote destinations require HTTPS on port 443; +- redirects are disabled; +- request and response bodies have independent size limits; and +- unknown configuration fields are rejected. + +### Secret-store adapters + +Secret stores sit behind the Rust `SecretProvider` interface. An adapter +receives an opaque reference chosen by local policy and returns a +`SecretString`; callers never choose a backend item. + +The binary currently includes: + +- `environment`, intended only for disposable local development; and +- `vaultwarden`, using an isolated Bitwarden CLI session and exact item UUID + mappings. + +More backends can be added without changing the workload protocol. Providers +are compiled into the binary and selected by trusted realm configuration; +Charon does not load credential-handling plugins dynamically or fall back to a +different provider during an outage. The extension rules are documented in +[ADR 0004](docs/adr/0004-secret-provider-adapters.md). + +## Project boundaries + +Charon owns the request-time data path: manifest verification, local policy, +credential lookup, injection, proxying, and redacted audit events. + +It does not own: + +- user, workspace, or lease management; +- issuance of workload manifests; +- secret-store provisioning and backup; +- deployment or network policy; or +- human-approval workflows. + +Those systems integrate through signed data and versioned contracts; Charon +does not query an application's database on the request path. See the +[integration boundary map](docs/integration-boundaries.md) and +[machine-readable contracts](contracts/README.md). + +## Security + +Charon handles credentials, so changes to authorization, proxying, provider +adapters, TLS, or logging deserve careful review. Please read +[`SECURITY.md`](SECURITY.md) before reporting a vulnerability and see the +[threat model](docs/threat-model.md) for the detailed guarantees, assumptions, +and remaining risks. + +The full local quality gate is: + +```sh +mise run check +``` + +## Documentation + +- [Forward-proxy protocol](contracts/forward-proxy.md) +- [Configuration and integration boundaries](docs/integration-boundaries.md) +- [Threat model](docs/threat-model.md) +- [Deployment guide](docs/deployment.md) +- [Architecture decisions](docs/adr/) +- [Human-approval contracts](contracts/README.md) + +## License + +Charon is licensed under the [Apache License 2.0](LICENSE). diff --git a/examples/charon.dev.toml b/examples/charon.dev.toml new file mode 100644 index 0000000..2e7c263 --- /dev/null +++ b/examples/charon.dev.toml @@ -0,0 +1,35 @@ +# Local startup configuration. This file contains no real credentials and does +# not configure HTTPS interception. See charon.toml for the operator example. +listen = "127.0.0.1:3129" + +[realm] +id = "local-development" +tenant = "example" +persona = "developer" +generation = 1 + +[provider] +kind = "environment" + +[identity] +issuer = "example-control-plane" +audience = "charon" +# Public test key only. The matching private key is not part of this example. +public_key = "11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=" +max_ttl_seconds = 60 +clock_skew_seconds = 2 + +[[capabilities]] +name = "local-demo" +persona = "developer" +service = "local-api" +methods = ["GET"] +paths = ["/demo"] + +[[services]] +name = "local-api" +hosts = ["127.0.0.1"] +header = "authorization" +placeholder = "Bearer charon-placeholder" +value_template = "Bearer {secret}" +secret_ref = "CHARON_DEMO_TOKEN" diff --git a/mise.toml b/mise.toml index 3e1d66a..1fcdbd1 100644 --- a/mise.toml +++ b/mise.toml @@ -33,6 +33,6 @@ run = "cargo test --all-targets --all-features" description = "Check dependency advisories, licenses, bans, and sources" run = "cargo deny check" -[tasks.run] -description = "Run Charon with the example development policy" -run = "cargo run -- --config examples/charon.toml" +[tasks.dev] +description = "Run Charon with the local development policy" +run = "cargo run -- --config examples/charon.dev.toml" From 6611939f6a3c3bd17a1ef20a07b2af7ae5a6a1cd Mon Sep 17 00:00:00 2001 From: Alexander Ververis Date: Tue, 28 Jul 2026 03:39:03 +0700 Subject: [PATCH 2/5] Add hosted Charon artwork to README --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 3bb6b37..b2a46a6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # Charon +

+ Charon ferrying souls across the river Styx +

+ Charon is a forward proxy that adds credentials to approved outbound requests. It lets a workload call an API without putting the API credential in that workload's environment, filesystem, or container image. From f4e89da296407b4ff75241433c28835f2f3769f0 Mon Sep 17 00:00:00 2001 From: Alexander Ververis Date: Tue, 28 Jul 2026 03:41:22 +0700 Subject: [PATCH 3/5] Position Charon for AI agent security --- Cargo.toml | 2 +- README.md | 30 +++++++++++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3fd1b8b..3febed3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "charon" version = "0.1.0" edition = "2024" rust-version = "1.97" -description = "Credential-injecting forward proxy for workloads" +description = "Credential-injecting forward proxy for AI agents and other workloads" license = "Apache-2.0" repository = "https://github.com/ak5/charon" publish = false diff --git a/README.md b/README.md index b2a46a6..81092f3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # Charon +

+ CI status + Container image + Rust 1.97 or newer + Apache 2.0 license +

+

Date: Tue, 28 Jul 2026 12:36:40 +0700 Subject: [PATCH 4/5] Relicense Charon under MIT --- Cargo.toml | 2 +- LICENSE | 30 +++++++++++++++++------------- README.md | 12 +++++++----- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3febed3..b5b3928 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" rust-version = "1.97" description = "Credential-injecting forward proxy for AI agents and other workloads" -license = "Apache-2.0" +license = "MIT" repository = "https://github.com/ak5/charon" publish = false diff --git a/LICENSE b/LICENSE index 34873a0..cb4d4b5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,17 +1,21 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +MIT License - Copyright 2026 Alexander Ververis +Copyright (c) 2026 Alexander Ververis - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - http://www.apache.org/licenses/LICENSE-2.0 +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 81092f3..304bc5c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ CI status Container image Rust 1.97 or newer - Apache 2.0 license + MIT license

@@ -41,9 +41,11 @@ integration contracts are still being refined before a production release. ## Why use it? -Tools often need credentials for services such as GitHub, package registries, -or internal APIs. Giving every tool a long-lived token makes that token -available to the tool and to anything that compromises it. +AI agents and other software often need authenticated access to external +services such as email, calendars, cloud platforms, customer-support systems, +payment providers, or an organization's own APIs. Giving every workload a +long-lived credential makes that credential available to the workload and to +anything that compromises it. Charon moves the credential into a smaller, separately operated process. A request is allowed only when all of these agree: @@ -209,4 +211,4 @@ mise run check ## License -Charon is licensed under the [Apache License 2.0](LICENSE). +Charon is licensed under the [MIT License](LICENSE). From dd084e87d7f69eca85841d77e685c1b352f6d0cf Mon Sep 17 00:00:00 2001 From: Alexander Ververis Date: Wed, 29 Jul 2026 04:45:53 +0700 Subject: [PATCH 5/5] chore: bootstrap contributor workflows (#24) --- .agents/skills/doc-code-parity/SKILL.md | 24 +++++++++++ .agents/skills/pr-create/SKILL.md | 18 ++++++++ .agents/skills/pr-review/SKILL.md | 17 ++++++++ .agents/skills/teach-debug/SKILL.md | 16 +++++++ .agents/skills/teach-dev/SKILL.md | 16 +++++++ .agents/skills/teach-git/SKILL.md | 19 +++++++++ .agents/skills/teach-repo/SKILL.md | 18 ++++++++ .agents/skills/teach-security/SKILL.md | 22 ++++++++++ .agents/skills/teach-ship/SKILL.md | 20 +++++++++ .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/bug.yml | 33 +++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 +++ .github/ISSUE_TEMPLATE/feature.yml | 26 ++++++++++++ .github/PULL_REQUEST_TEMPLATE/release.md | 20 +++++++++ .github/copilot-instructions.md | 3 ++ .github/pull_request_template.md | 13 +++++- .github/scripts/check-pr-path.sh | 32 ++++++++++++++ .github/workflows/pr-policy.yml | 20 +++++++++ AGENTS.md | 40 ++++++++++++------ CLAUDE.md | 38 +++++++++++++++-- CONTRIBUTING.md | 52 +++++++++++++++++++++++ README.md | 2 + docs/conventions.md | 54 ++++++++++++++++++++++++ docs/index.md | 33 +++++++++++++++ 24 files changed, 524 insertions(+), 18 deletions(-) create mode 100644 .agents/skills/doc-code-parity/SKILL.md create mode 100644 .agents/skills/pr-create/SKILL.md create mode 100644 .agents/skills/pr-review/SKILL.md create mode 100644 .agents/skills/teach-debug/SKILL.md create mode 100644 .agents/skills/teach-dev/SKILL.md create mode 100644 .agents/skills/teach-git/SKILL.md create mode 100644 .agents/skills/teach-repo/SKILL.md create mode 100644 .agents/skills/teach-security/SKILL.md create mode 100644 .agents/skills/teach-ship/SKILL.md create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE/release.md create mode 100644 .github/copilot-instructions.md create mode 100755 .github/scripts/check-pr-path.sh create mode 100644 .github/workflows/pr-policy.yml create mode 100644 CONTRIBUTING.md create mode 100644 docs/conventions.md create mode 100644 docs/index.md diff --git a/.agents/skills/doc-code-parity/SKILL.md b/.agents/skills/doc-code-parity/SKILL.md new file mode 100644 index 0000000..7d0f536 --- /dev/null +++ b/.agents/skills/doc-code-parity/SKILL.md @@ -0,0 +1,24 @@ +--- +name: doc-code-parity +description: Check Charon documentation against code, contracts, configuration, and workflows. +--- + +# Documentation and code parity + +Default to the current diff. Use a full sweep when explicitly requested. + +1. Read `docs/conventions.md` and its documentation ownership table. +2. Inspect changed source, contracts, examples, deployment files, workflows, + and `mise.toml`. +3. Compare behavior with the owning documents. Search exact flags, fields, + endpoints, commands, host rules, and provider names with `rg`. +4. Distinguish a stale document from an implementation regression. Preserve + security intent; do not normalize a code regression into prose. +5. Update `docs/threat-model.md` whenever a trust boundary or credential flow + changes. +6. Check all relative Markdown links and run `mise run check`. +7. Report corrected drift and unresolved judgment calls separately. + +For a full sweep, include `README.md`, `CONTRIBUTING.md`, `SECURITY.md`, +`docs/`, `contracts/`, `.github/`, `deploy/`, `examples/`, `integration/`, +`src/`, `tests/`, and `mise.toml`. diff --git a/.agents/skills/pr-create/SKILL.md b/.agents/skills/pr-create/SKILL.md new file mode 100644 index 0000000..65bd180 --- /dev/null +++ b/.agents/skills/pr-create/SKILL.md @@ -0,0 +1,18 @@ +--- +name: pr-create +description: Prepare an honest Charon pull request using the staged release policy. +--- + +# Create a pull request + +1. Read `CONTRIBUTING.md`, `docs/conventions.md`, and the current diff. +2. Confirm the branch started from `dev`. Ordinary work MUST target `dev`; + only `dev` releases and `hotfix/*` repairs target `main`. +3. Preserve intent, decisions, evidence, caveats, known warts, excluded + follow-up work, security impact, documentation parity, risk, and rollback. +4. Run `$doc-code-parity` and `mise run check`. +5. Review commits and changed files for secrets or unrelated work. +6. Push and create the PR only when authorized. Never merge it in this skill. + +Use `.github/PULL_REQUEST_TEMPLATE.md` for ordinary changes and +`.github/PULL_REQUEST_TEMPLATE/release.md` for `dev` to `main`. diff --git a/.agents/skills/pr-review/SKILL.md b/.agents/skills/pr-review/SKILL.md new file mode 100644 index 0000000..e702ac4 --- /dev/null +++ b/.agents/skills/pr-review/SKILL.md @@ -0,0 +1,17 @@ +--- +name: pr-review +description: Review a Charon PR for correctness, security, documentation, and policy. +--- + +# Review a pull request + +1. Read `AGENTS.md`, `CONTRIBUTING.md`, and the pull request diff. +2. Verify the branch path with `.github/scripts/check-pr-path.sh`. +3. Prioritize credential exposure, authorization bypass, caller-selected + references, wildcard destinations, redirects, unsafe logging, and + orchestrator coupling. +4. Check tests for failure paths and synthetic credentials only. +5. Run `$doc-code-parity`; require a threat-model update for boundary changes. +6. Run `mise run check`. +7. Report findings in severity order with paths and evidence. State when no + findings remain and identify residual test gaps. diff --git a/.agents/skills/teach-debug/SKILL.md b/.agents/skills/teach-debug/SKILL.md new file mode 100644 index 0000000..2e4a888 --- /dev/null +++ b/.agents/skills/teach-debug/SKILL.md @@ -0,0 +1,16 @@ +--- +name: teach-debug +description: Teach safe, evidence-led debugging of Charon. +--- + +# Learn safe debugging + +1. Reproduce with synthetic credentials and the smallest relevant test. +2. Separate observations, hypotheses, and conclusions. +3. Inspect configuration validation, manifest verification, capability + matching, provider lookup, upstream handling, and redaction in order. +4. Never weaken deny-by-default checks to make a test pass. +5. Never print credential values; use sanitized metadata as evidence. +6. Add a regression test, update owned documentation, and run + `mise run check`. +7. Recover by reverting the focused change or applying a corrective commit. diff --git a/.agents/skills/teach-dev/SKILL.md b/.agents/skills/teach-dev/SKILL.md new file mode 100644 index 0000000..55070ef --- /dev/null +++ b/.agents/skills/teach-dev/SKILL.md @@ -0,0 +1,16 @@ +--- +name: teach-dev +description: Teach safe setup, development, and verification for Charon. +--- + +# Learn local development + +1. Explain that `mise install` installs pinned Rust and `cargo-deny`. +2. Use `mise run dev` with `examples/charon.dev.toml`. +3. Clarify that the environment provider is only for synthetic local values. +4. Make focused changes and name affected contracts and documentation. +5. Run focused Cargo tests while iterating, then `mise run check`. +6. Inspect the diff and test output before approving a commit or push. + +Never place real credentials in environment files, examples, fixtures, logs, +or repository-root `tmp/`. diff --git a/.agents/skills/teach-git/SKILL.md b/.agents/skills/teach-git/SKILL.md new file mode 100644 index 0000000..d9f9ad1 --- /dev/null +++ b/.agents/skills/teach-git/SKILL.md @@ -0,0 +1,19 @@ +--- +name: teach-git +description: Teach Charon's dev-to-main Git workflow and verification habits. +--- + +# Learn the Git workflow + +Explain state before mutation. Use `git status`, `git diff`, and `git log` as +evidence. + +- Feature branches start from `dev`; ordinary PRs target `dev`. +- Maintainers squash one result or deliberately rebase an independently valid + commit series. +- Release PRs go from `dev` to `main` and MUST use a merge commit. +- Hotfixes target `main`, then `main` is merged back into `dev`. +- Shared history is never rewritten. + +Teach the operator to inspect the exact diff, run checks, and approve pushing +or opening a PR separately. Recover with revert or a corrective commit. diff --git a/.agents/skills/teach-repo/SKILL.md b/.agents/skills/teach-repo/SKILL.md new file mode 100644 index 0000000..f876236 --- /dev/null +++ b/.agents/skills/teach-repo/SKILL.md @@ -0,0 +1,18 @@ +--- +name: teach-repo +description: Orient a nontechnical operator to Charon and safe AI-assisted work. +--- + +# Learn the repository + +Guide the operator through outcomes, not command memorization. + +1. Explain Charon using `README.md`: workloads request approved capabilities; + Charon injects credentials without returning them. +2. Use `docs/index.md` as the map. Show `src/`, `contracts/`, `docs/`, + `examples/`, `deploy/`, and `tests/`. +3. Explain the fail-closed rules in `AGENTS.md`. +4. Demonstrate asking an agent for a small change, requesting evidence, + inspecting the diff, and withholding approval when impact is unclear. +5. Explain that `mise run check` is the complete local quality gate. +6. Point to the other `$teach-*` skills for task-specific guidance. diff --git a/.agents/skills/teach-security/SKILL.md b/.agents/skills/teach-security/SKILL.md new file mode 100644 index 0000000..8c49666 --- /dev/null +++ b/.agents/skills/teach-security/SKILL.md @@ -0,0 +1,22 @@ +--- +name: teach-security +description: Teach Charon credential flows, trust boundaries, and security review. +--- + +# Learn Charon security + +Read `docs/threat-model.md` and `docs/integration-boundaries.md`. Trace a request +from signed manifest through capability policy, provider lookup, credential +injection, upstream transport, and redacted response handling. + +Ask: + +- Can the workload select a secret reference or destination? +- Does exact host, method, and path authorization fail closed? +- Could a redirect send credentials to another hop? +- Could logs, errors, serialization, snapshots, or fixtures expose a value? +- Does the data plane depend on an orchestrator or application database? +- Is each new trust boundary and credential flow documented and tested? + +Use synthetic credentials. Require `mise run check` and a threat-model update +when the boundary changes. diff --git a/.agents/skills/teach-ship/SKILL.md b/.agents/skills/teach-ship/SKILL.md new file mode 100644 index 0000000..4a21c2c --- /dev/null +++ b/.agents/skills/teach-ship/SKILL.md @@ -0,0 +1,20 @@ +--- +name: teach-ship +description: Teach how verified Charon changes move through dev into production. +--- + +# Learn to ship + +For ordinary work, reconcile intent, diff, tests, documentation, risk, +rollback, and excluded follow-up work. Run `$doc-code-parity` and +`mise run check`, then use `$pr-create` for a PR to `dev`. + +For a release, record the `dev` source SHA, open `dev` to `main`, wait for fresh +checks, and use a merge commit. After merge, verify: + +```sh +git merge-base --is-ancestor main +``` + +Production images originate from `main`. Merging, publishing, and deploying +each require explicit operator authority. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..6eb528e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @ak5 diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..f6c6e26 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,33 @@ +name: Bug report +description: Report reproducible incorrect behavior that is not a vulnerability +title: "bug: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: Do not include credentials or vulnerability details. Report security issues privately using SECURITY.md. + - type: textarea + id: behavior + attributes: + label: What happened? + description: Describe the observed and expected behavior. + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Reproduction + description: Provide a minimal reproduction using synthetic credentials only. + validations: + required: true + - type: input + id: version + attributes: + label: Charon version or commit + validations: + required: true + - type: textarea + id: context + attributes: + label: Additional context + description: Include sanitized logs or configuration when useful. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0b414fd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Report a vulnerability privately + url: https://github.com/ak5/charon/security/advisories/new + about: Do not disclose vulnerabilities, credentials, or exploit details in a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 0000000..3c82b1a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,26 @@ +name: Feature request +description: Propose a capability or improvement +title: "feature: " +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: Problem + description: What outcome is difficult or impossible today? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed behavior + description: Describe the externally visible behavior and boundaries. + validations: + required: true + - type: textarea + id: security + attributes: + label: Security considerations + description: Identify affected trust boundaries, credential flows, destinations, or authorization decisions. + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE/release.md b/.github/PULL_REQUEST_TEMPLATE/release.md new file mode 100644 index 0000000..b7625c4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/release.md @@ -0,0 +1,20 @@ +# Release + +## Included changes + +- Release source SHA: +- Notable changes: +- Known limitations: + +## Release checks + +- [ ] The base branch is `main` and the head branch is `dev`. +- [ ] All required checks pass on the current `dev` head. +- [ ] Documentation and security-impact notes are current. +- [ ] The release will use a merge commit, not squash or rebase. +- [ ] After merging, `git merge-base --is-ancestor main` succeeds. + +## Risk and rollback + +- Risk: +- Rollback: diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..fd1d16f --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,3 @@ +Follow the complete repository contract in `AGENTS.md`. Treat it as +authoritative for security invariants, commands, documentation ownership, +scratch files, branches, and releases. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1f7ed94..5d08873 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,14 +1,25 @@ ## Summary +## Motivation and scope + ## Security impact - Trust boundary changed: no - Credential flow changed: no - Threat model updated or not required: +## Documentation + +- Documentation updated or not required: + ## Verification - [ ] `mise run check` -Release PRs are opened from `dev` to protected production branch `main`. +## Risk and rollback + +- Risk: +- Rollback: +Ordinary pull requests target `dev`. Use the release template for `dev` to +`main`. diff --git a/.github/scripts/check-pr-path.sh b/.github/scripts/check-pr-path.sh new file mode 100755 index 0000000..fb15779 --- /dev/null +++ b/.github/scripts/check-pr-path.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -eu + +base=${1:-} +head=${2:-} + +if [ -z "$base" ] || [ -z "$head" ]; then + printf 'error: usage: check-pr-path.sh \n' >&2 + exit 2 +fi + +case "$base:$head" in + main:dev) + printf 'valid release path: dev -> main; use a merge commit\n' + ;; + main:hotfix/*) + printf 'valid hotfix path: %s -> main; use a merge commit and then merge main -> dev\n' "$head" + ;; + main:*) + printf 'error: main accepts only dev releases or hotfix/* emergency repairs\n' >&2 + exit 1 + ;; + dev:main) + printf 'valid hotfix reconciliation path: main -> dev; use a merge commit\n' + ;; + dev:*) + printf 'valid integration path: %s -> dev; use squash or an approved rebase merge\n' "$head" + ;; + *) + printf 'information: no staged-release path rule applies to %s -> %s\n' "$head" "$base" + ;; +esac diff --git a/.github/workflows/pr-policy.yml b/.github/workflows/pr-policy.yml new file mode 100644 index 0000000..bfd4faa --- /dev/null +++ b/.github/workflows/pr-policy.yml @@ -0,0 +1,20 @@ +name: PR policy + +on: + pull_request: + types: [opened, reopened, synchronize, edited] + +permissions: + contents: read + +jobs: + branch-path: + name: branch-path + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Validate pull request branch path + env: + BASE_BRANCH: ${{ github.event.pull_request.base.ref }} + HEAD_BRANCH: ${{ github.event.pull_request.head.ref }} + run: .github/scripts/check-pr-path.sh "$BASE_BRANCH" "$HEAD_BRANCH" diff --git a/AGENTS.md b/AGENTS.md index 720af8d..6514a4f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,20 +3,34 @@ Charon is a credential-handling security boundary. Prefer a small, explicit, fail-closed design over convenience. -- Never commit, log, snapshot, or fixture real credentials. -- Destination access is deny-by-default and uses exact hostnames. Do not add - wildcard or caller-selected credential references. -- Credential values must use secret-holding types and must never implement +- Agents MUST NOT commit, log, snapshot, or fixture real credentials. +- Destination access MUST remain deny-by-default and use exact hostnames. + Agents MUST NOT add wildcard or caller-selected credential references. +- Credential values MUST use secret-holding types and MUST NOT implement `Debug`, serialization, or response conversion. -- Redirects remain disabled unless every hop is independently re-authorized. -- Keep the data plane independent of any orchestrator. Integrators issue signed - workload identity and policy; Charon does not query an application's database - or control-plane API. -- Update `docs/threat-model.md` when a trust boundary or credential flow changes. -- Run `mise run check` before committing. +- Redirects MUST remain disabled unless every hop is independently re-authorized. +- The data plane MUST remain independent of any orchestrator. Integrators issue + signed workload identity and policy; Charon does not query an application's + database or control-plane API. +- Agents MUST update `docs/threat-model.md` when a trust boundary or credential + flow changes. +- Agents MUST run `mise run check` before committing. + +Read [the documentation index](docs/index.md), [repository +conventions](docs/conventions.md), and [the contribution +policy](CONTRIBUTING.md) before changing public contracts or workflows. + +Use `tmp//` for disposable task artifacts. Agents MUST NOT use `tmp/` +for credentials. Promote retained work to its owned tracked path and remove only +scratch files created by the current task. Branch workflow: -- Create feature branches from `dev` and target ordinary pull requests to `dev`. -- Only release pull requests from `dev` target `main`. -- Treat `main` as production and release immutable container versions from it. +- Agents MUST create feature branches from `dev` and target ordinary pull + requests to `dev`. +- Only release pull requests from `dev` CAN target `main`. +- Maintainers MUST merge `dev` into `main` with a merge commit to preserve + ancestry. +- Agents MUST treat `main` as production and release immutable container + versions from it. +- Agents MUST NOT rewrite shared branch history. diff --git a/CLAUDE.md b/CLAUDE.md index 522b813..6514a4f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,36 @@ -# Claude Guidance +# Agent Guidance -Follow `AGENTS.md`. Charon handles credentials, so maintain deny-by-default -policy, redacted observability, exact destination matching, and fail-closed -behavior. Run `mise run check` before proposing changes. +Charon is a credential-handling security boundary. Prefer a small, explicit, +fail-closed design over convenience. +- Agents MUST NOT commit, log, snapshot, or fixture real credentials. +- Destination access MUST remain deny-by-default and use exact hostnames. + Agents MUST NOT add wildcard or caller-selected credential references. +- Credential values MUST use secret-holding types and MUST NOT implement + `Debug`, serialization, or response conversion. +- Redirects MUST remain disabled unless every hop is independently re-authorized. +- The data plane MUST remain independent of any orchestrator. Integrators issue + signed workload identity and policy; Charon does not query an application's + database or control-plane API. +- Agents MUST update `docs/threat-model.md` when a trust boundary or credential + flow changes. +- Agents MUST run `mise run check` before committing. + +Read [the documentation index](docs/index.md), [repository +conventions](docs/conventions.md), and [the contribution +policy](CONTRIBUTING.md) before changing public contracts or workflows. + +Use `tmp//` for disposable task artifacts. Agents MUST NOT use `tmp/` +for credentials. Promote retained work to its owned tracked path and remove only +scratch files created by the current task. + +Branch workflow: + +- Agents MUST create feature branches from `dev` and target ordinary pull + requests to `dev`. +- Only release pull requests from `dev` CAN target `main`. +- Maintainers MUST merge `dev` into `main` with a merge commit to preserve + ancestry. +- Agents MUST treat `main` as production and release immutable container + versions from it. +- Agents MUST NOT rewrite shared branch history. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..06d0cbb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing to Charon + +Thank you for helping improve Charon. Because Charon handles credentials, +security boundaries receive the same care as implementation details. + +## Before changing code + +Read the [documentation index](docs/index.md), [repository +conventions](docs/conventions.md), [security policy](SECURITY.md), and +[threat model](docs/threat-model.md). Never use real credentials in code, +tests, logs, examples, captures, or fixtures. + +Install the pinned tools and run the complete local quality gate: + +```sh +mise install +mise run check +``` + +## Pull requests + +Create a feature branch from `dev` and open the pull request against `dev`. +A pull request is a proposal, not an entitlement to merge. Merge-ready work has +a clear purpose, focused scope, passing checks, documentation parity, and an +explicit account of security impact. + +Maintainers SHOULD squash a change that represents one result. They CAN use a +rebase merge when every preserved commit is independently coherent and passes +the required checks. Work that is not merge-ready can be revised, split, +rewritten on a maintainer-owned `integrate/pr-N` branch, or declined. +Maintainers MUST NOT silently rewrite a contributor-owned branch. Material +rewrites require fresh CI and review. + +The final commit records the primary author. Use `Co-authored-by` trailers for +material contributions retained in that commit. If maintainers replace an +implementation rather than retain it, the pull request discussion and release +notes SHOULD acknowledge the original proposal without claiming false commit +authorship. + +## Releases and hotfixes + +Only a release pull request from `dev` targets `main`. Maintainers MUST use a +merge commit for that pull request so `dev` remains an ancestor of `main`. +Squash and rebase release merges are prohibited. + +Emergency `hotfix/*` branches CAN target `main`. After merging a hotfix, +maintainers MUST merge `main` back into `dev` before the next release. + +## Security reports + +Do not open a public issue for a vulnerability. Follow [SECURITY.md](SECURITY.md) +and use GitHub private vulnerability reporting. diff --git a/README.md b/README.md index 304bc5c..a21a297 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,8 @@ mise run check ## Documentation +- [Documentation index](docs/index.md) +- [Contributing](CONTRIBUTING.md) - [Forward-proxy protocol](contracts/forward-proxy.md) - [Configuration and integration boundaries](docs/integration-boundaries.md) - [Threat model](docs/threat-model.md) diff --git a/docs/conventions.md b/docs/conventions.md new file mode 100644 index 0000000..5fa4b8a --- /dev/null +++ b/docs/conventions.md @@ -0,0 +1,54 @@ +# Repository conventions + +## Repository shape and commands + +Charon is a single Rust package with contracts, deployment examples, integration +fixtures, and durable design documentation in the same repository. Cargo owns +Rust build and test behavior. `mise.toml` pins Rust and `cargo-deny` and provides +stable operator tasks across formatting, linting, tests, deployment-contract +tests, and dependency policy. This cross-tool quality gate is why +`mise run check` exists. + +Repository-root `tmp/` is an ignored workspace for disposable artifacts. Use +`tmp//` to avoid collisions. Never store credentials there. Move +anything durable to its owned tracked path. + +## Branch and release policy + +`dev` is the integration and default branch. Ordinary pull requests target +`dev`. Maintainers SHOULD squash a single-result change and CAN rebase an +approved commit series whose commits pass independently. + +`main` is production. Only `dev` release pull requests and emergency +`hotfix/*` pull requests target it. Releases MUST use merge commits because +squash or rebase would break the shared ancestry required for repeated +`dev`-to-`main` promotion. After a hotfix, maintainers MUST merge `main` back +into `dev`. + +Both long-lived branches require pull requests and block force pushes and +deletion. The project does not use a merge queue. + +## Documentation ownership + +| Changed surface | Documentation owner | +| --- | --- | +| Public purpose, quick start, supported provider summary | `README.md` | +| HTTP and workload wire behavior | `contracts/` | +| Configuration schema or integration responsibility | `docs/integration-boundaries.md` and examples | +| Trust boundary, credential flow, authorization, redirects, or logging | `docs/threat-model.md` | +| Operator deployment behavior | `docs/deployment.md` | +| Approval broker behavior | `contracts/approval-*` and `docs/approval-broker-operations.md` | +| Architectural decision | `docs/adr/` | +| Contributor, branch, merge, or release policy | `CONTRIBUTING.md` and this document | +| Commands or tool versions | `mise.toml`, `README.md`, and this document | + +## Repository bootstrap + +- Profile: Full public Rust security service. +- Canonical entry points: `README.md`, `docs/index.md`, and `CONTRIBUTING.md`. +- Enabled capabilities: conventions, agent operation, teaching, documentation + parity, PR workflow, Vibe Session readiness, GitHub hygiene, automation, and + environment. +- Vibe Session readiness: enabled. +- Starter/template adoption: intentionally omitted because this is an + established Rust codebase. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..eb65585 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,33 @@ +# Documentation + +Start with the document that owns the question you are trying to answer. + +## Use and operate Charon + +- [README](../README.md): product overview and fastest development path. +- [Deployment](deployment.md): deployment configuration and operating steps. +- [Approval broker operations](approval-broker-operations.md): approval-broker + integration and operational behavior. +- [Vertical integration example](../integration/vertical/README.md): isolated + end-to-end demonstration. + +## Understand the design + +- [Integration boundaries](integration-boundaries.md): system ownership and + external integration boundaries. +- [Threat model](threat-model.md): security invariants, trust boundaries, and + known risks. +- [Persona and realm contract](persona-realm-contract.md): current pre-1.0 + identity context. +- [Architecture decisions](adr/): decisions and their rationale. +- [Machine-readable contracts](../contracts/README.md): protocol schemas, + OpenAPI documents, and canonicalization rules. + +## Contribute + +- [Contributing](../CONTRIBUTING.md): branch, review, attribution, release, and + security-reporting policy. +- [Repository conventions](conventions.md): commands, repository shape, + documentation ownership, scratch space, and bootstrap choices. +- [Security audit](security-audit-2026-07-27.md): current audit evidence and + residual risks.