Where It Appears In The Edge AI Architecture
A3 appears after authentication, when the gateway maps a result ID to stored inference state.
Threat Model
The attacker is authenticated but not authorized for the target object.
Attacker capability
The attacker has a legitimate account or lab token and can call result routes that are meant for normal users. They can observe their own result IDs, infer naming patterns, change path parameters, and compare status codes and response shapes.
Security assumption being tested
Authentication proves who the caller is. It does not prove that the caller can read, delete, compare, export, or rerun a specific result object. A3 tests whether every object lookup is scoped by user, project, tenant, role, and action.
Assets at risk
Uploaded image metadata, prediction labels, confidence scores, backend choice, latency records, model version, experiment notes, admin logs, and saved benchmark comparisons between Jetson and Zynq.
Out of scope
No real account probing, no third-party API testing, no credential abuse, and no access attempts against systems you do not own. The included code uses two toy users and synthetic local result records.
Attack Intuition
A3 begins where A1 ends: the caller may be logged in, but object ownership is still unproven.
A user submits an image and receives a result such as R-1001. The browser later calls GET /api/v1/results/R-1001. If the server simply loads whichever object ID appears in the URL, changing the ID to R-2002 may return another user's inference record. This is the classic broken object-level authorization pattern applied to ML result storage.
The bug is easy to miss because the route already has authentication. Unit tests may prove that anonymous requests are blocked, while never proving that a logged-in user is constrained to their own objects. In research portals, that mistake can leak not only predictions but experimental metadata: which backend ran, which model hash was used, how long the accelerator took, and whether a sample is part of a sensitive dataset.
Safe framing: the demonstration below is a localhost toy service with hard-coded users and synthetic records. It exists to teach the defensive check, not to test external APIs.
Technical Explanation
Object authorization must be part of the query and the policy decision, not an afterthought.
Vulnerable sequence
- Gateway validates the bearer token and extracts
user-a. - Route receives
result_id=R-2002from the client path. - Database query loads
R-2002by ID alone and returns it.
Secure sequence
- Gateway validates token, role, tenant, and project context.
- Object query is scoped by
result_idandowner_idor equivalent policy. - Cross-user reads return 403 or indistinguishable 404, with safe audit logging.
Edge AI-specific detail
The object may not be just a row. It can reference an image blob, feature vector, model version, backend route, Zynq timing trace, or Jetson TensorRT engine metadata. Ownership must cover the whole object graph.
A robust implementation avoids trusting client-supplied IDs as the security boundary. Use opaque IDs for enumeration resistance, but do not confuse unpredictability with authorization. The real control is policy: subject, action, object, owner, tenant, and purpose.
Mathematical Formulation
A3 is a missing predicate in the authorization relation.
The secure check is not "does this ID exist?" It is "does this subject have this action on this object under the current policy?" The experimental goal is to prove that changing the ID changes only the authorization decision, not the user's effective access.
Step-By-Step Safe Lab Demonstration
The lab contrasts authenticated ID-only access with authenticated owner-scoped access.
- Save the Python code from the next section as
a3_object_authorization_lab.py. - Start vulnerable mode with
python3 a3_object_authorization_lab.py --mode vulnerable. - Request
R-1001using User A's token. This should succeed because User A owns it. - Request
R-2002using User A's token. Vulnerable mode incorrectly returns User B's synthetic result. - Restart with
python3 a3_object_authorization_lab.py --mode secure. - Repeat the cross-user request. Secure mode returns 403 and logs a safe object-authorization denial.
Interactive toy replay
Replay the expected local behavior without starting the Python server. The output is illustrative and sends no requests.
Full Code For A Local Simulated Lab
The server binds to 127.0.0.1 and uses toy tokens, users, and synthetic result objects only.
#!/usr/bin/env python3
"""
A3 local-only simulator: broken object authorization.
This toy API binds to 127.0.0.1 and uses synthetic result objects.
It demonstrates why authentication is not enough: every object lookup
must also enforce ownership or an equivalent authorization policy.
"""
import argparse
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlparse
TOKENS = {
"Bearer token-user-a": {"user_id": "user-a", "role": "researcher", "tenant": "edge-lab"},
"Bearer token-user-b": {"user_id": "user-b", "role": "researcher", "tenant": "edge-lab"},
"Bearer token-admin": {"user_id": "admin-1", "role": "admin", "tenant": "edge-lab"},
}
RESULTS = {
"R-1001": {
"owner_id": "user-a",
"tenant": "edge-lab",
"label": "synthetic-cat",
"confidence": 0.934,
"backend": "jetson",
"latency_ms": 47,
"model_hash": "onnx-demo-7ad3",
},
"R-2002": {
"owner_id": "user-b",
"tenant": "edge-lab",
"label": "synthetic-dog",
"confidence": 0.881,
"backend": "zynq",
"latency_ms": 38,
"model_hash": "bit-demo-19fe",
},
}
def can_read_result(principal, result):
if principal["role"] == "admin" and principal["tenant"] == result["tenant"]:
return True
return principal["user_id"] == result["owner_id"] and principal["tenant"] == result["tenant"]
class ResultHandler(BaseHTTPRequestHandler):
server_version = "A3LocalGateway/1.0"
def log_message(self, fmt, *args):
print("%s - %s" % (self.log_date_time_string(), fmt % args))
def _send_json(self, status, document):
body = json.dumps(document, indent=2).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _principal(self):
token = self.headers.get("Authorization", "")
return TOKENS.get(token)
def do_GET(self):
parsed = urlparse(self.path)
parts = [part for part in parsed.path.split("/") if part]
if len(parts) != 4 or parts[:3] != ["api", "v1", "results"]:
self._send_json(404, {"error": "not found"})
return
principal = self._principal()
if not principal:
self._send_json(401, {"error": "missing or invalid bearer token"})
print("event=reject reason=missing_auth route=/api/v1/results")
return
result_id = parts[3]
result = RESULTS.get(result_id)
if not result:
self._send_json(404, {"error": "result not found"})
print("event=object_miss user=%s result_id=%s" % (principal["user_id"], result_id))
return
if self.server.mode == "secure" and not can_read_result(principal, result):
self._send_json(403, {"error": "object access denied"})
print(
"event=object_denied user=%s result_id=%s owner=%s action=read"
% (principal["user_id"], result_id, result["owner_id"])
)
return
response = {"result_id": result_id, "result": result}
self._send_json(200, response)
print(
"event=object_read mode=%s user=%s result_id=%s owner=%s backend=%s"
% (self.server.mode, principal["user_id"], result_id, result["owner_id"], result["backend"])
)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--mode", choices=["vulnerable", "secure"], default="vulnerable")
parser.add_argument("--port", type=int, default=8081)
args = parser.parse_args()
server = ThreadingHTTPServer(("127.0.0.1", args.port), ResultHandler)
server.mode = args.mode
print("A3 local lab listening on http://127.0.0.1:%s mode=%s" % (args.port, args.mode))
print("Toy tokens: Bearer token-user-a, Bearer token-user-b, Bearer token-admin")
server.serve_forever()
if __name__ == "__main__":
main()
# Terminal 1: intentionally vulnerable toy service
python3 a3_object_authorization_lab.py --mode vulnerable
# User A reads their own result.
curl -s http://127.0.0.1:8081/api/v1/results/R-1001 \
-H "Authorization: Bearer token-user-a"
# User A reads User B's result in vulnerable mode. This is the local-only A3 demonstration.
curl -i -s http://127.0.0.1:8081/api/v1/results/R-2002 \
-H "Authorization: Bearer token-user-a"
# Terminal 1: secure toy service
python3 a3_object_authorization_lab.py --mode secure
# User A still reads their own result.
curl -s http://127.0.0.1:8081/api/v1/results/R-1001 \
-H "Authorization: Bearer token-user-a"
# Cross-user read is blocked by object ownership policy.
curl -i -s http://127.0.0.1:8081/api/v1/results/R-2002 \
-H "Authorization: Bearer token-user-a"
Practical Example For Your Edge AI + FPGA + Jetson Setup
A3 protects result records produced by both private backends.
Expected topology
The Pi gateway creates a result object after dispatching inference to Jetson or Zynq. The object may contain prediction output, backend name, latency, model hash, input digest, and experiment tag. That object should be attached to a user, project, tenant, and retention policy.
A3 failure mode
User A submits to Jetson and sees R-1001. User B submits to Zynq and gets R-2002. If User A can fetch R-2002, the Pi leaks another user's prediction and backend metadata without any direct backend exposure.
In your portal, the object graph matters. A result object can point to stored images, trace bundles, model versions, and accelerator benchmark records. Secure design should authorize each object family consistently, not only the top-level result row.
Observable Signals Or Logs
A3 should be visible as object-scope denies, not backend events.
| Signal | Vulnerable observation | Hardened observation |
|---|---|---|
| HTTP status | 200 when user changes result ID to another user's object | 403 or policy-shaped 404 for cross-user object access |
| Gateway log | event=object_read user=user-a result_id=R-2002 owner=user-b | event=object_denied user=user-a result_id=R-2002 action=read |
| Backend log | No new inference needed; data leaks from result store | No backend call; denial occurs at Pi result API |
| Database query | SELECT by result_id only | SELECT scoped by result_id plus owner, tenant, project, or policy join |
| Detection metric | Repeated object misses or cross-owner reads may blend into normal 200s | Object-denied counter, user/object mismatch rate, and suspicious ID-walk patterns |
Impact Analysis
A3 is mostly a confidentiality and integrity issue, with research-process consequences.
Confidentiality
Cross-user reads expose predictions, confidence scores, sample metadata, backend routing, model hashes, and possibly links to uploaded images or trace bundles.
Integrity
If write, delete, rerun, export, or label-update routes share the same flaw, one user can manipulate another user's experimental records or training feedback.
Availability
Availability is secondary, but unauthorized delete or rerun operations can remove results, trigger extra backend work, or corrupt experiment queues.
Mapping To CIA, STRIDE, PASTA, And MITRE ATLAS
A3 is an API authorization problem that can expose AI model behavior and data.
| Framework | A3 mapping | Research interpretation |
|---|---|---|
| CIA | Confidentiality and Integrity primary; Availability secondary | Protect result objects and related artifacts from cross-user read, write, delete, export, and rerun actions. |
| STRIDE | Elevation of Privilege, Information Disclosure, Tampering, Repudiation | A normal user gains object-level access beyond their policy and may leave insufficient audit evidence. |
| PASTA | Stage 3 decomposition, Stage 4 threat analysis, Stage 5 vulnerability analysis, Stage 6 attack modeling | Decompose result, image, log, model-version, and benchmark objects; test each action with cross-user fixtures. |
| OWASP API Security | API1:2023 Broken Object Level Authorization | Every endpoint that receives an object ID and acts on a record needs object-level authorization, not only authentication. |
| MITRE ATLAS | Relevant to model exposure and exfiltration-through-inference concerns, including AML.T0024 Exfiltration via ML Inference API when result access exposes model behavior or inference outputs. | A3 can leak AI outputs and model metadata from stored objects rather than live inference alone. |
Defense Mapping To Existing D1-D11 Controls
D1 is the primary control because it includes both authentication and object checks.
| Control | Role against A3 | Validation |
|---|---|---|
| D1 JWT Auth + Object Checks | Primary control. Validate token, derive subject context, and enforce object ownership or policy on every result action. | Cross-user reads, exports, deletes, reruns, and label updates return 403 or policy-shaped 404. |
| D6 Sanitized Logging | Records object-denied events without storing tokens, request bodies, or sensitive images. | Logs include user id, action, object id hash, owner mismatch, tenant, status, and trace id. |
| D5 Query Anomaly Detection | Detects ID walking, repeated object misses, and abnormal result-export patterns. | Alert on high object-denied rates or many neighboring result IDs from one user. |
| D4 Private Backend Subnet | Does not fix A3 directly, but ensures result access remains mediated by the Pi gateway rather than backend side routes. | Client VLAN cannot fetch backend-local result or benchmark files. |
Research Notes: What To Measure Experimentally
A3 is well suited for automated cross-user authorization tests.
Object action matrix
For each object type, test read, list, export, delete, compare, rerun, and annotate. The expected result is own object allowed, cross-user object denied, admin scoped by policy, and unrelated tenant denied.
Query-level enforcement
Measure whether secure queries use owner or policy scope at the database layer, not just post-query filtering. Post-query checks can still leak timing and existence signals.
Enumeration resistance
Compare sequential IDs and opaque IDs. Opaque IDs reduce guessability, but the authorization test must still fail safely if a valid cross-user ID is known.
Audit quality
Measure object-denied counts, ID-walk patterns, cross-owner mismatch rates, and whether logs contain enough context for incident response without leaking payloads.
Key Takeaways
Authentication is a starting point, not an object-level access decision.
- A3 occurs when the gateway loads objects by client-supplied ID without proving the caller may act on that object.
- Result objects in Edge AI carry sensitive model behavior, backend routing, and experiment metadata, not just labels.
- D1 must enforce object ownership or policy on every read, list, export, delete, rerun, and annotation route.
- Automated tests should include cross-user fixtures for every object family in the portal.