From 907fd8e36827e92a1113e7b92a608a8bc3665ad9 Mon Sep 17 00:00:00 2001 From: Colby Farley Date: Thu, 9 Apr 2026 21:45:45 -0500 Subject: [PATCH 1/2] tighten credential-path proof boundaries --- src/azurefox/chains/credential_path.py | 42 ++++++++++++++++++++++++++ src/azurefox/render/table.py | 6 +++- tests/test_terminal_ux.py | 41 +++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/azurefox/chains/credential_path.py b/src/azurefox/chains/credential_path.py index db63350..4e93069 100644 --- a/src/azurefox/chains/credential_path.py +++ b/src/azurefox/chains/credential_path.py @@ -351,6 +351,11 @@ def _build_candidate_record( target_names=target_names, target_visibility_issue=visibility_issue, next_review=semantic.next_review, + confidence_boundary=_candidate_confidence_boundary( + target_service=target_service, + target_resolution=target_resolution, + target_names=target_names, + ), summary=_candidate_summary( env=env, target_service=target_service, @@ -438,6 +443,43 @@ def _candidate_summary( return summary +def _candidate_confidence_boundary( + *, + target_service: str, + target_resolution: str, + target_names: list[str], +) -> str: + if target_resolution == "visibility blocked": + return ( + f"AzureFox cannot name the downstream {target_service} under current target-side " + "visibility; do not treat this as a confirmed credential path." + ) + + if target_resolution == "narrowed candidates": + candidate_count = max(len(target_names), 1) + return ( + f"AzureFox narrowed this to {candidate_count} visible {target_service} candidate(s), " + "but has not yet proved the exact target or a working credential." + ) + + if target_resolution == "tenant-wide candidates": + return ( + f"AzureFox can only narrow this to a broad visible {target_service} set so far; " + "the exact target and working credential remain unconfirmed." + ) + + if target_resolution == "service hint only": + return ( + f"AzureFox only has a service hint for this {target_service} path so far; the " + "downstream target and working credential remain unconfirmed." + ) + + return ( + f"AzureFox has not yet proved the exact downstream {target_service} target or a working " + "credential." + ) + + def _target_visibility_note(target_label: str, issues: list[CollectionIssue]) -> str | None: if not issues: return None diff --git a/src/azurefox/render/table.py b/src/azurefox/render/table.py index 05af613..9064825 100644 --- a/src/azurefox/render/table.py +++ b/src/azurefox/render/table.py @@ -1959,7 +1959,11 @@ def _chains_note(item: dict, *, family: str = "") -> str: target_service = str(item.get("target_service") or "target") confidence_boundary = str(item.get("confidence_boundary") or "").strip() - if family == "credential-path" and target_service == "keyvault" and confidence_boundary: + if ( + family == "credential-path" + and confidence_boundary + and resolution != "named target not visible" + ): return confidence_boundary if resolution == "named match": diff --git a/tests/test_terminal_ux.py b/tests/test_terminal_ux.py index da5ec63..fb9dd5b 100644 --- a/tests/test_terminal_ux.py +++ b/tests/test_terminal_ux.py @@ -781,8 +781,8 @@ def test_chains_table_mode_surfaces_priority_and_next_review(tmp_path: Path) -> assert "this secret." in result.stdout assert "Check vault access" in normalized_output assert "connection clues." in normalized_output - assert "This app exposes a" in normalized_output - assert "secret-shaped" in normalized_output + assert "AzureFox narrowed" in normalized_output + assert "database" in normalized_output def test_deployment_chains_table_mode_surfaces_source_oriented_columns(tmp_path: Path) -> None: @@ -952,7 +952,6 @@ def test_chains_keyvault_note_prefers_current_identity_access_sentence() -> None } rendered = render_table("chains", payload) - normalized = " ".join(rendered.split()) assert "Your current identity" in normalized @@ -960,6 +959,42 @@ def test_chains_keyvault_note_prefers_current_identity_access_sentence() -> None assert "Named target matched visible inventory." not in rendered +def test_chains_named_keyvault_not_visible_prefers_inventory_boundary() -> None: + payload = { + "metadata": {"command": "chains"}, + "family": "credential-path", + "paths": [ + { + "priority": "low", + "urgency": "bookmark", + "asset_name": "func-orders", + "setting_name": "PAYMENT_API_KEY", + "target_service": "keyvault", + "target_resolution": "named target not visible", + "target_names": [], + "next_review": "Verify that the named target is visible in current inventory.", + "confidence_boundary": ( + "AzureFox can name the vault, but cannot yet tell whether your current " + "identity can read the secret." + ), + "summary": ( + "FunctionApp 'func-orders' pulls 'PAYMENT_API_KEY' from Key Vault " + "'hidden-vault.vault.azure.net', but AzureFox cannot see that vault in " + "current inventory." + ), + } + ], + "issues": [], + } + + rendered = render_table("chains", payload) + normalized = " ".join(rendered.split()) + + assert "This app names a Key" in normalized + assert "current inventory." in normalized + assert "cannot yet tell whether your current identity can read the secret" not in normalized + + def test_app_services_partial_read_surfaces_collection_issue() -> None: payload = { "metadata": {"command": "app-services"}, From ca89c79a6d2e2c351141200ca8a5f517624c7aa4 Mon Sep 17 00:00:00 2001 From: Colby Farley Date: Thu, 9 Apr 2026 21:51:06 -0500 Subject: [PATCH 2/2] chore: refresh pr metadata