SECURITY.md
Rama main · réplica de lectura del repositorio original.
# Security Policy
Kadran's entire reason for existing is a security property, so security reports
are treated as first-class work, not as an interruption.
## Supported versions
| Version | Supported |
|---|---|
| `main` | ✅ |
| tagged releases through `v0.5.1` | Existing releases; upgrade to a fixed current build for new security changes |
Until a `v1.0.0` tag exists, only `main` receives fixes.
## Reporting a vulnerability
**Please do not open a public issue for a security problem.**
Use GitHub's private reporting:
> **[Report a vulnerability](https://github.com/erkanrzgc/kadran/security/advisories/new)**
If that is unavailable to you, email **benerkanrzgc@gmail.com** with `KADRAN SECURITY`
in the subject line.
### What to include
The more of this you can provide, the faster it gets fixed:
- The version or commit SHA you tested
- Which component is affected — `kadrand`, `kadran-exec`, `kadran-connect`, the CLI,
`kadran-proxy`, the Electron app, the systemd units, or the bootstrap path
- A concrete reproduction: exact request, command, or `authorized_keys` line
- What boundary you believe it crosses (see below)
- Whether you needed prior access, and at what privilege level
### Response
- **Acknowledgement:** within 72 hours
- **Initial assessment:** within 7 days
- **Fix or documented mitigation:** target 30 days for anything crossing a
privilege boundary
This is a single-maintainer project, so these are honest targets rather than a
contractual SLA. You will get a real answer, including "this is not a
vulnerability, and here is why."
## What counts as a vulnerability
Kadran's security model rests on specific, testable boundaries. Anything that
crosses one of these is in scope and will be treated as high severity:
1. **`kadrand` reaching Docker or root.** The daemon runs as an unprivileged user,
is not in the `docker` group, and has an empty capability bounding set. Any path
by which it obtains privileged capability is the most severe class in this
project.
2. **Escaping the executor's schema.** `proto/kadran/v1/exec.proto` is the whitelist.
If a request can reach a privileged operation that the schema was supposed to make
unrepresentable — a container Kadran does not manage, an arbitrary image, a host
path, a free-form argv — that is in scope.
3. **Bypassing `kadran-connect`.** The forced command plus `restrict` should make the
client key incapable of anything but running that one binary. A shell, a tunnel,
or a second command is in scope.
4. **Forging or breaking the audit chain.** Writing a record attributed to another
actor, deleting a record without `kadran audit verify` detecting it (other than
the known cross-chain gap listed below), or forging the SSH fingerprint carried
in the connection preamble.
5. **Reading environment values or build arguments** from the audit log (both are
written as `[REDACTED]`) or from process memory.
6. **Cross-application escape** — one deployed app reaching another app's volumes,
network, or environment.
7. **A deploy key exceeding its scope.** A key whose `authorized_keys` line carries
`kadran-connect -deploy=<apps>` should reach only `Ping` and `Deploy`, and
`Deploy` only for those apps. Calling any other RPC, deploying an app outside
the list, or a malformed line falling back to admin rights is in scope (K-131).
8. **Escaping the proxy capability broker.** `kadrand` may submit typed domain/IP/port
routes to `kadran-proxy`; it cannot reach Caddy's raw admin socket. Arbitrary Caddy
handlers, `{file.…}` response bodies, admin/listener changes and Unix/loopback
upstreams are outside that capability. Bypassing the broker is in scope (K-143).
The only non-proxy handler it accepts is a fixed-shape 308 redirect whose target
must be a routed domain (K-152).
9. **Leaking private repository credentials.** Opt-in repository credentials belong to
the executor's server configuration. They must not enter application definitions,
the desktop, daemon database, Docker context, audit records or error messages.
## Explicitly out of scope
These are known and documented limitations, not undisclosed weaknesses:
- **Secrets are sealed at rest, and plaintext inside running containers.** Since
v0.5.0 environment values are age-sealed to the executor's root-only key before
persistence. The daemon cannot decrypt them. `docker inspect` and application
processes still see runtime values; backups and pre-migration copies made before
sealing can retain plaintext. See the [vault boundary](README.md#secrets-are-sealed-with-the-executors-key).
- **Proxy routing is an administrative capability.** The broker restricts upstreams
to IPv4 addresses in `172.16.0.0/12`, matching the daemon's Docker network sandbox,
and rejects the network (`x.y.0.0`), gateway (`x.y.0.1`, the host itself) and
broadcast (`x.y.255.255`) address of each `/16` block. Cloud VPC ranges that
overlap `172.16.0.0/12` and the gateway of a custom smaller Docker subnet are not
excluded. It does not attest that a given IP belongs to a particular container. An
authorized or compromised daemon can redirect app traffic within that pool.
Module omission alone is insufficient: Caddy's core `static_response` can expand
file placeholders; isolation of raw admin and the typed broker provide the boundary.
- **A compromised `kadrand` can redirect names that already point at the server.** It can
308-redirect such a name to any domain it has itself routed first (K-152, ruling S1). The
broker bounds redirect targets to the routed set, not to the operator's intent. It needs a
compromised control plane and exposes no key, file or exec capability.
- **Application data requires application-aware backup and deployment.** Control
database backups exclude volumes. Volume archives read live files; they are not
transactional snapshots. Replicas and overlapping releases share named volumes.
Use an external database or an application-specific maintenance/backup procedure
for single-writer databases; one replica alone does not prevent release overlap.
- **The two audit chains are not compared with each other.** Each is verified on
its own, so a compromised `kadrand` that drops records from *its own* chain is
not detected. Closing this needs the executor to return its record hash
(`docs/decisions.md`, K-079). Tampering with the *executor's* chain remains in
scope.
- **Root on the server can do anything.** Kadran defends against a compromised
*panel*, not against an attacker who already holds root.
- **Desktop mutation confirmation is an operator safeguard.** Named IPC methods,
validation, sandboxing and server-side authorization limit the bridge. Confirmation
is not a second authorization factor; destructive actions still have no TOTP gate.
- **Anyone in the `kadran-client` group can talk to `api.sock`.** That is the
design; group membership is the authorization boundary and is set up by
`bootstrap`. Note the two groups are distinct and the distinction is
load-bearing: `kadran-client` reaches `api.sock`, while `exec.sock` sits in a
`0750 root:kadran` directory the client group cannot traverse at all.
Membership must be the user's *primary* group — `SO_PEERCRED` reports
only that, so adding a second admin with `usermod -aG` yields a silent
denial rather than access.
- **A deploy key controls the apps in its scope.** The code it deploys reads that
app's environment and volumes, and a Dockerfile can print build arguments into
the streamed build log. The scope protects other apps and administrative
actions, not the secrets of the apps the key may deploy. `kadrand` has no
outbound network, so it cannot check which branch a commit is on: a deploy key
can deploy any commit the configured repository serves, including commits from
open pull requests. Use it only in workflows triggered by pushes to your default
branch, never in `pull_request_target`.
- **A malicious operator.** Kadran produces a tamper-evident audit trail; it does
not prevent an authorized human from taking authorized destructive actions.
- Denial of service by resource exhaustion from a legitimately deployed app.
- Findings from automated scanners with no demonstrated impact.
## Disclosure
Coordinated disclosure. Once a fix is available, the advisory is published with
credit to the reporter unless you prefer to remain anonymous. There is no bug
bounty — this is an unfunded open-source project — but every valid report is
credited in the advisory and in `docs/decisions.md` alongside the measurement that
proved the fix.