Skip to content

fix(errors): 403 auth rejections no longer read as policy blocks - #197

Merged
saurabhjain1592 merged 1 commit into
mainfrom
fix/2861-403-auth-misclassification
Aug 3, 2026
Merged

fix(errors): 403 auth rejections no longer read as policy blocks#197
saurabhjain1592 merged 1 commit into
mainfrom
fix/2861-403-auth-misclassification

Conversation

@saurabhjain1592

@saurabhjain1592 saurabhjain1592 commented Jul 10, 2026

Copy link
Copy Markdown
Member

Release-blocking library fix from the getaxonflow/axonflow-enterprise#2861 release-readiness gate.

Bug

Every agent error envelope carries a literal "blocked" JSON key - a tenant-mismatch rejection is:

{"success":false,"error":"Tenant mismatch","blocked":false}   // HTTP 403

so handleErrorResponse's substring heuristic

case 403:
  if (body.contains("policy") || body.contains("blocked")) {
    throw new PolicyViolationException(errorMessage);
  }

classified every 403 authorization rejection as PolicyViolationException. Callers following the documented pattern (policy block = expected, non-fatal outcome) silently swallowed real auth failures with exit 0 - exactly how examples/basic passed the smoke with a wrong client-id/user-token tenant pairing.

Fix

The 403 branch parses the JSON body (Jackson, already in use) and treats a present blocked boolean as authoritative:

  • "blocked":true -> PolicyViolationException
  • "blocked":false -> AuthenticationException (HTTP 403)
  • unparseable body, or no blocked boolean -> legacy policy-phrase fallback (policy / block_reason; the bare blocked substring no longer counts)

Audited the rest of the method: this was the only substring-classification site - 401 always maps to AuthenticationException; 402/409/429/408/504 key on status code alone.

Tests

  • Unit (WireMock): exact live tenant-mismatch envelope -> AuthenticationException with message intact; blocked:true envelope -> PolicyViolationException; unparseable-body fallback in both directions.
  • New live-stack leg runtime-e2e/error_classification_403/ (no skip hatch) asserting both directions through proxyLLMCall.

Live runtime-e2e evidence (enterprise stack, platform v9.6.1, agent :8080, original pre-rebase round)

Against this fix:

PASS [tenant-mismatch-403] AuthenticationException: Tenant mismatch
PASS [policy-block-403] PolicyViolationException: Request blocked by policy: Detects stacked DROP TABLE/DATABASE statement
RESULT: PASS (2/2)
EXIT=0

Same leg against the released 8.5.1 jar (proves the bug + the leg's sensitivity):

FAIL: REGRESSION: tenant-mismatch 403 classified as PolicyViolationException (Request blocked by policy: Tenant mismatch) - the "blocked":false envelope must map to AuthenticationException
EXIT=1

Rebase note (2026-08-03, backlog clearance)

Rebased onto main after the 9.0.0 release, #203 (jackson 2.22.1) and #196 (example pins to published 9.0.0). The original 8.5.2 release prep no longer applies and was dropped in the rebase:

  • pom.xml stays at main's 9.0.0 - versioning belongs to the next release cut (published 9.0.0 does NOT contain this fix; per the semver policy the operator decides the next version, noting this changes the exception type thrown for 403 auth rejections).
  • Example pom pins stay at fix(examples): pin poms to published 9.0.0; fail loud on tenant mismatch [skip-runtime-e2e] #196's published 9.0.0 (they pin published artifacts; pinning an unpublished version would break mvn package for users).
  • The CHANGELOG entry now sits under [Unreleased] ### Fixed instead of a [8.5.2] heading.

The library fix, unit tests and runtime-e2e leg are unchanged from the reviewed diff.

Refs getaxonflow/axonflow-enterprise#2861.

saurabhjain1592 added a commit that referenced this pull request Aug 3, 2026
8.5.1 went stale mid-flight: Maven Central now serves 9.0.0
(released 2026-07-18, com.getaxonflow:axonflow-sdk latest/release).
The 9.0.0 breaking change is confined to the LangGraph MCP adapter
(connector_type/tool split), which none of the four examples touch;
all four build clean against the published 9.0.0 artifact from a
fresh local repository.

The Basic.java comment is updated to 'SDK <= 9.0.0': the 403
body.contains("blocked") misclassification is still present in the
published 9.0.0 (verified in the v9.0.0 tag and reproduced against
the published jar), so the tenant-mismatch fail-loud workaround
remains required until the library fix (#197) ships.

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
saurabhjain1592 added a commit that referenced this pull request Aug 3, 2026
…tch [skip-runtime-e2e] (#196)

* fix(examples): pin poms to SDK 8.5.1; fail loud on tenant-mismatch 403

Release-gate smoke against a live enterprise stack (v9.6.1, epic #2861)
surfaced two example-level issues:

- Example poms pinned stale SDK versions (basic at 6.1.0, the rest at
  8.5.0), so smoke runs resolved remote Maven artifacts instead of the
  locally built 8.5.1 under test. Pin all four to 8.5.1.

- basic swallowed a 403 tenant-mismatch rejection as a policy block and
  exited 0: the agent's error body carries a literal "blocked":false
  key, which trips the SDK's handleErrorResponse body.contains("blocked")
  heuristic and misclassifies the 403 as PolicyViolationException. Until
  the library fix ships, the example treats a Tenant mismatch message as
  the auth failure it is and exits 1 with a pointer to the
  AXONFLOW_CLIENT_ID/user-token tenant pairing requirement.

Verified against the live stack: all four examples exit 0 with real
LLM round-trips, and basic exits 1 on a deliberately mismatched
client-id/token pairing.

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

* chore(ci): retrigger DoD gate after skip-runtime-e2e escape hatch

No-diff commit: the DoD workflow only runs on opened/synchronize/reopened,
so the [skip-runtime-e2e] title + justification body edit needs a fresh
synchronize event to be evaluated against the updated payload.

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

* fix(examples): refresh example pom pins to published 9.0.0

8.5.1 went stale mid-flight: Maven Central now serves 9.0.0
(released 2026-07-18, com.getaxonflow:axonflow-sdk latest/release).
The 9.0.0 breaking change is confined to the LangGraph MCP adapter
(connector_type/tool split), which none of the four examples touch;
all four build clean against the published 9.0.0 artifact from a
fresh local repository.

The Basic.java comment is updated to 'SDK <= 9.0.0': the 403
body.contains("blocked") misclassification is still present in the
published 9.0.0 (verified in the v9.0.0 tag and reproduced against
the published jar), so the tenant-mismatch fail-loud workaround
remains required until the library fix (#197) ships.

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

---------

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
Release-gate smoke for getaxonflow/axonflow-enterprise#2861 found that
every agent error envelope carries a literal "blocked" JSON key (a
tenant-mismatch rejection is
{"success":false,"error":"Tenant mismatch","blocked":false}), so
handleErrorResponse's body.contains("policy") || body.contains("blocked")
substring heuristic classified EVERY 403 authorization rejection as
PolicyViolationException. Callers following the documented pattern of
treating policy blocks as an expected, non-fatal outcome silently
swallowed real auth failures (wrong client-id/user-token tenant pairing)
with exit 0.

The 403 branch now parses the JSON body and treats a present "blocked"
boolean as authoritative: true -> PolicyViolationException, false ->
AuthenticationException. Only unparseable or blocked-less bodies fall
back to the policy-phrase heuristic (policy / block_reason; the bare
"blocked" substring no longer counts). This is the only substring
classification site in the method — 401 always maps to
AuthenticationException and the remaining branches key on status code
alone.

Tests: exact live tenant-mismatch envelope -> AuthenticationException;
blocked:true envelope -> PolicyViolationException; unparseable-body
fallback both ways (1316 unit tests green). New live-stack leg
runtime-e2e/error_classification_403/ asserts both directions through
proxyLLMCall against a real enterprise agent; it fails against the
8.5.1 jar and passes against this build.

Release prep for 8.5.2: pom.xml bumped, CHANGELOG [Unreleased] renamed
to [8.5.2] - 2026-07-10 with the fix noted, example poms pinned to
8.5.2 per the 8.5.1 release convention (CI re-syncs them to the parent
version at run time).

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
@saurabhjain1592
saurabhjain1592 force-pushed the fix/2861-403-auth-misclassification branch from 50c25ca to 0ef9a25 Compare August 3, 2026 17:30
@saurabhjain1592

Copy link
Copy Markdown
Member Author

Backlog-clearance verification (rebased head 0ef9a25):

Rebase onto current main (post 9.0.0 release, #203 jackson 2.22.1, #196 example pins to published 9.0.0). Conflict resolution dropped the stale 8.5.2 release prep: pom.xml stays at main's 9.0.0, example pins stay at #196's published 9.0.0, and the CHANGELOG entry moved under [Unreleased] ### Fixed. The library fix, unit tests and runtime-e2e/error_classification_403/ leg are unchanged from the reviewed diff.

The fix still binds - both discriminating directions run locally on the rebased head (JDK 17):

  • shouldHandle403BlockedFalseAsAuthRejection: exact live tenant-mismatch envelope {"success":false,"error":"Tenant mismatch","blocked":false} -> AuthenticationException, not PolicyViolationException. PASS.
  • shouldHandle403BlockedTrueAsPolicyViolation: {"success":false,"blocked":true,"block_reason":"Detects stacked DROP TABLE/DATABASE statement"} -> PolicyViolationException. PASS.
  • Plus both unparseable-body fallback tests and the pre-existing shouldHandle403PolicyViolation: 5/5 PASS.

Mutation proof: with the fix hunk reverted to the old substring heuristic (if (body.contains("policy") || body.contains("blocked"))), the mutant compiled and the suite ran: shouldHandle403BlockedFalseAsAuthRejection FAILED (misclassified as PolicyViolationException) while shouldHandle403BlockedTrueAsPolicyViolation still passed - the test pins exactly the auth-rejection direction. Fix restored, 5/5 green again, and the full AxonFlowTest class (171 tests) plus the whole suite (1325 tests, 0 failures) pass on the rebased head.

CI: all 17 checks green on 0ef9a25, including Contract Integration (WireMock), real-stack ubuntu/macos/windows, Validate Wire Shape, Validate Version Alignment, CodeQL and DCO: https://github.com/getaxonflow/axonflow-sdk-java/actions/runs/30837069806 https://github.com/getaxonflow/axonflow-sdk-java/actions/runs/30837069865

Note for the next release cut: published 9.0.0 does NOT contain this fix, and the fix changes the exception type thrown for 403 auth rejections (PolicyViolationException -> AuthenticationException), which per the semver policy is the operator's versioning call.

Merging per backlog-clearance brief.

@saurabhjain1592
saurabhjain1592 merged commit 8cb11a8 into main Aug 3, 2026
18 checks passed
@saurabhjain1592
saurabhjain1592 deleted the fix/2861-403-auth-misclassification branch August 3, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant