feat(sip): M6 CP4 — per-endpoint auth selection (unauth trunk + authed bridge coexist) - #152
Merged
Merged
Conversation
added 2 commits
July 17, 2026 04:01
Replace the all-credentials UNION challenge with per-endpoint auth SELECTION: match the inbound request to an endpoint (via type=identify) and issue a digest challenge ONLY if the MATCHED endpoint has an auth section. An unauthenticated trunk (matched, no auth) and an authenticated bridge (matched, auth) now coexist on one transport. Falls back to the union only when no identify match exists (no type=identify configured), preserving the pre-CP4 default. Selection is strictly narrower than the old union (which accepted ANY configured credential), so an authed endpoint is never weakened. Receiver-side proof (e2e_per_endpoint_auth.rs): a trunk request (source 127.0.0.2, no auth) is accepted with NO 401 datagram; a bridge request (source 127.0.0.3, auth) WITHOUT creds gets a 401 datagram and is rejected, and WITH a valid digest is accepted. RED: revert to the union -> the trunk is challenged (returns None) -> scenario (a) goes RED.
…ial review)
The per-endpoint selection used unwrap_or_default() on the matched endpoint's
auth lookup, which collapsed two misconfigurations to an EMPTY credential set
and then SKIPPED the challenge — accepting a credential-less caller (a fail-open
regression vs the old union, which still challenged via other endpoints' creds):
(a) the matched endpoint has auth=<name> but no auth section named <name>
resolves (typo / removed section, dangling reference);
(b) an identify's endpoint=<name> names a non-existent endpoint.
Both now fail CLOSED with 403 Forbidden. A matched endpoint with NO auth section
still correctly proceeds unchallenged (the unauthenticated trunk).
Adds fail-closed scenarios to e2e_per_endpoint_auth (folded into the single test
since the binary shares process-global config). RED: revert to unwrap_or_default
-> the dangling-auth endpoint accepts a credential-less INVITE (Some(call_id)).
Owner
Author
Adversarial review disposition (codex refused the diff — OpenAI safety filter flagged auth code as 'cybersecurity'; fell back to an independent adversarial Claude reviewer per review-routing)The reviewer confirmed the selection logic is strictly narrower than the old union (a hardening) and the test is a genuine receiver-side digest round-trip. One MAJOR finding, FIXED (f9f8a64):
Confirmed non-weakening: verify matches a cred by username against the single matched endpoint's cred; no cross-endpoint credential acceptance; the None/union fallback is only reachable when NO identify rules exist at all (else source_acl 403s first); no cross-context escalation. CI green; workspace test + clippy green locally. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
M6 CP4 — per-endpoint auth selection (unauth trunk + authed bridge coexist)
FINDING. rustisk challenged an inbound request against the UNION of every configured
credential — so if ANY endpoint had auth, EVERY request was challenged. That breaks the topology
where an UNAUTHENTICATED trunk (a carrier, matched by source IP, no digest) and an AUTHENTICATED
bridge (matched by source IP, digest required) share one transport.
FIX. Per-endpoint auth SELECTION: the inbound request is matched to an endpoint (via
type=identify), and a digest challenge is issued ONLY if the MATCHED endpoint has an auth section.challenge, proceed.
type=identifyconfigured) → fall back to the union of authed endpoints'credentials, preserving the pre-CP4 default and still challenging a digest endpoint.
This is strictly NARROWER than the old union (which accepted any configured credential), so an
authed endpoint is never weakened — it still requires a valid digest against its OWN credential.
RECEIVER-SIDE PROOF (
e2e_per_endpoint_auth.rs). Two endpoints on one transport identified bydistinct source IPs — trunk (127.0.0.2, no auth) and bridge (127.0.0.3, auth). Asserting on the
actual response datagram + accept/reject outcome:
CAPTURED RED. Revert to the all-credentials union (challenge whenever any endpoint has auth):
(the trunk gets challenged → returns None → coexistence broken).
Scope note: this is authz/RFC correctness with NO secret-in-log and NO brute-force control — kept
that way deliberately (per the brief, so the review can go to codex).
GATES.
cargo test --workspace --exclude pjsip-shimgreen;cargo clippy --workspace --exclude pjsip-shim -- -D warningsclean. Rust 1.97.0.