Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/azurefox/chains/credential_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion src/azurefox/render/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
41 changes: 38 additions & 3 deletions tests/test_terminal_ux.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -952,14 +952,49 @@ 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
assert "can read this secret." in normalized
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"},
Expand Down
Loading