You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENTS.md
+17-29Lines changed: 17 additions & 29 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,6 +21,9 @@ Source of truth for architecture decisions: `docs/adr/README.md` and linked ADRs
21
21
4.**Keep explicit root-domain fallback behavior through SNI no-route handling to admin/API listener.**
22
22
- Why: preserves intended control-plane vs tenant routing split (ADR-0001).
23
23
24
+
5.**All leases require TLS=true.** The register endpoint rejects `TLS=false`. The `RegisterRequest.TLS` field exists but non-TLS leases are not permitted.
25
+
- Why: enforces end-to-end transport security for all tenant routes.
26
+
24
27
## Security and Anti-Abuse Invariants
25
28
26
29
1.**Admin-managed policy is authoritative for runtime security controls.**
@@ -37,16 +40,17 @@ Source of truth for architecture decisions: `docs/adr/README.md` and linked ADRs
37
40
38
41
## TLS and Identity Invariants
39
42
40
-
1.**Relay holds the TLS private key; SDK/tunnel never does.** SDK calls `/v1/sign` on the relay via `RemoteSigner` for all private key operations.
41
-
- Why: prevents key material leakage to untrusted tunnel endpoints.
43
+
1.**Relay holds the TLS private key for admin/API (root domain) only.** SDK calls `/v1/sign` on the relay via `RemoteSigner` for admin/API TLS termination. For SNI-passthrough routes, the relay peeks the ClientHello for SNI then bridges the raw encrypted connection — the backend/tunnel endpoint terminates TLS and holds those keys, not the relay.
44
+
- Why: admin/API key material stays on the relay; tenant TLS passthrough avoids key distribution to the relay entirely.
42
45
43
46
2.**mTLS is implicit (optional) for `/sdk/*` control-plane paths.** When a client cert is presented, the relay validates it (CertBind stage). When absent, CertBind is skipped and token auth alone is used.
44
47
-`KEYLESS_DIR` env var presence triggers SDK lifecycle identity issuance and client cert presentation. When unset, the SDK operates in token-only mode.
45
-
- Keyless TLS (`RemoteSigner` for `/v1/sign`) is independent of mTLS — always used for TLS termination regardless of client cert presence.
48
+
- Keyless TLS (`RemoteSigner` for `/v1/sign`) is independent of mTLS — always used for admin/API TLS termination regardless of client cert presence.
46
49
- Why: ADR-0003 admission order is IP ban → Lease → [CertBind if cert present] → Token. Invalid certs are still rejected; absent certs skip CertBind.
47
50
48
51
3.**All relay URLs must be `https://`.**`NormalizeRelayAPIURL` rejects non-HTTPS. SDK and tunnel hard-fail on `http://`.
49
52
- Why: enforces transport security without opt-out.
53
+
50
54
4.**`keyless_tls/` is published to `github.com/gosuda/keyless_tls`** and vendored as a directory for co-development. Root `go.mod` pins a specific pseudo-version or tag — no `replace` directive.
51
55
- Why: enables external consumers while keeping co-development convenient. Pin version after pushing upstream changes.
52
56
@@ -55,38 +59,28 @@ Source of truth for architecture decisions: `docs/adr/README.md` and linked ADRs
55
59
1.**SNI wildcard matching is one-level only.**`sni.Router.GetRoute()` checks `*.parent.example.com` for `foo.parent.example.com` — not arbitrary depth.
2.**Protocol markers on reverse TCP connections:**`0x00` = keepalive, `0x01` = non-TLS start, `0x02` = TLS passthrough activation. The SNI router does true TLS passthrough — it peeks the ClientHello for routing, then bridges the raw encrypted connection without terminating TLS.
59
63
- Why: binary protocol, not discoverable from HTTP-layer code.
60
64
61
65
3.**HTTP/2 is intentionally disabled on the admin HTTP server** (`TLSNextProto: make(…)`).
62
66
- Why: the server hijacks connections for `/sdk/connect`; HTTP/2 multiplexing breaks hijack semantics.
63
67
64
-
## Operational Truths (CI-Aligned, Minimal)
65
-
66
-
1.**Local lint workflow: run `make lint-auto` first, then `make lint`.**
67
-
- Why: `lint-auto` applies safe rewrites locally, while `lint` is the strict non-mutating gate that matches CI.
68
+
## API Response Contract
68
69
69
-
2.**Use CI-equivalent verification when validating high-risk changes:**
70
-
-`make vet`
71
-
-`make lint`
72
-
-`make test`
73
-
-`make vuln`
74
-
- Why: these are the enforced checks in `.github/workflows/ci.yml`.
75
-
- Note: `make tidy` is a local maintenance/pre-release step and is not currently part of the CI workflow.
70
+
1.**All HTTP responses use the `APIEnvelope` wrapper:**`{ ok: bool, data?: any, error?: { code, message } }` (defined in `types/api.go`). Write responses through `writeAPIData()`, `writeAPIOK()`, or `writeAPIError()` helpers — never raw JSON.
71
+
- Why: cross-cutting contract across all endpoints; inconsistent envelopes break SDK and frontend parsing.
76
72
77
-
3.**Assume Go toolchain baseline from `go.mod`.**
78
-
- Why: CI resolves Go from `go.mod`; avoid stale version assumptions.
73
+
## Operational Truths (CI-Aligned, Minimal)
79
74
80
-
4.**Use `Makefile` as build and verification authority; do not reference absent tooling (for example, no `justfile` in this repo).**
81
-
- Why: reduces operational drift and broken command guidance.
75
+
1.**CI verification commands:**`make vet`, `make lint`, `make test`, `make vuln`. These are the enforced checks in `.github/workflows/ci.yml`. Note: `make tidy` is a local maintenance/pre-release step, not part of CI.
82
76
83
-
5.**`make build-server` does NOT call `make build-frontend`.** If called alone, `//go:embed dist/*` will be stale or empty. The Dockerfile calls both explicitly in order.
77
+
2.**`make build-server` does NOT call `make build-frontend`.** If called alone, `//go:embed dist/*` will be stale or empty. The Dockerfile calls both explicitly in order.
84
78
- Why: prevents silent broken builds with missing frontend assets.
85
79
86
-
6.**`admin_settings.json` persists in the process CWD**, not in `KEYLESS_DIR`. State is lost on container restart unless CWD is a mounted volume.
80
+
3.**`admin_settings.json` persists in the process CWD**, not in `KEYLESS_DIR`. State is lost on container restart unless CWD is a mounted volume.
87
81
- Why: prevents state-loss surprises in production.
88
82
89
-
7.**`onLeaseDeleted` has dual registration.**`portal/relay.go` registers one callback; `cmd/relay-server/main.go` overwrites it with a broader one (adds IP/BPS cleanup). The outer callback supersedes.
83
+
4.**`onLeaseDeleted` has dual registration.**`portal/relay.go` registers one callback; `cmd/relay-server/main.go` overwrites it with a broader one (adds IP/BPS cleanup). The outer callback supersedes.
90
84
- Why: coupling hazard — modifying either registration without understanding both breaks cleanup.
91
85
92
86
## Change Discipline
@@ -99,13 +93,7 @@ Source of truth for architecture decisions: `docs/adr/README.md` and linked ADRs
99
93
100
94
## Go Conventions
101
95
102
-
**Format:**`gofmt -w . && goimports -w .` before every commit. Imports: stdlib → external → internal (blank-line separated), local prefix `github.com/gosuda`.
103
-
104
-
**CGo:** always disabled — `CGO_ENABLED=0`. Pure Go only.
105
-
106
-
**Module:** commit `go.mod`+`go.sum`, never `go.work` · pin toolchain in `go.mod` · `go mod tidy && go mod verify && govulncheck ./...` pre-release · `os.Root` (Go 1.24+) for directory-scoped I/O.
107
-
108
-
**Concurrency:**`errgroup.Group` over `WaitGroup` · `errgroup.SetLimit` for bounded work · `context.WithTimeout` over `time.After` in loops (timer leak) · no bare `go func()` — creator owns lifecycle.
96
+
**Imports:** stdlib → external → internal (blank-line separated), local prefix `github.com/gosuda`. **Concurrency:**`errgroup.Group` over `WaitGroup` · `errgroup.SetLimit` for bounded work · `context.WithTimeout` over `time.After` in loops (timer leak) · no bare `go func()` — creator owns lifecycle. **I/O:**`os.Root` (Go 1.24+) for directory-scoped file operations.
0 commit comments