{
  "documentType": "codex-security.findings",
  "findings": [
    {
      "attackPath": {
        "dataflow": {
          "outcome": "The bank identity-binding endpoint mints an application bearer token without proving control of a bank-authenticated identity.",
          "sink": "app/api/service/Bank.php",
          "source": "route/app.php",
          "summary": "public bank binding request -> caller-selected identity fields -> staff attribute match -> relation write -> application bearer token"
        },
        "impact": {
          "level": "high",
          "why": "The bank identity-binding endpoint mints an application bearer token without proving control of a bank-authenticated identity."
        },
        "likelihood": {
          "level": "medium",
          "why": "An unauthenticated caller needs a valid staff name, personnel number, and school selector; the endpoint itself performs no bank-session ownership proof."
        },
        "limitations": [
          "An upstream bank gateway or reverse proxy could authenticate the path, but no such control appears in the repository."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": "route/app.php",
          "outcome": "The bank identity-binding endpoint mints an application bearer token without proving control of a bank-authenticated identity.",
          "summary": "An unauthenticated caller needs a valid staff name, personnel number, and school selector; the endpoint itself performs no bank-session ownership proof."
        },
        "summary": "An unauthenticated caller needs a valid staff name, personnel number, and school selector; the endpoint itself performs no bank-session ownership proof."
      },
      "confidence": {
        "level": "high",
        "rationale": "Direct repository evidence establishes the attacker input, missing or broken control, and security-relevant sink."
      },
      "extensions": {
        "candidateId": "candidate-ca286d3dc96d8443"
      },
      "findingId": "csf_a1eaedb819f3a94747cafc1c",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:8a70a36e73987b8e3bedfed1ea35a9b25bd07f3b138b7c52b7a92ca9170ece5b"
      },
      "identity": {
        "anchor": "bank-binding-without-identity-proof",
        "instance": "bank-bind-user-login-token-mint"
      },
      "locations": [
        {
          "endLine": 29,
          "path": "route/app.php",
          "role": "entrypoint",
          "startLine": 29
        },
        {
          "endLine": 166,
          "path": "app/api/controller/Bank.php",
          "role": "source",
          "startLine": 149
        },
        {
          "endLine": 15,
          "path": "app/api/controller/Bank.php",
          "role": "root_control",
          "startLine": 15
        },
        {
          "endLine": 299,
          "path": "app/api/service/Bank.php",
          "role": "sink",
          "startLine": 242
        }
      ],
      "occurrenceId": "occ_7b425b476f3e36e065722cb6",
      "preventiveControls": [
        "Central bank-session authentication middleware",
        "Server-side identity binding",
        "Single-use authorization state"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Accept the bank identity only from a server-verified authorization code/session, bind all identity fields to that result, and never trust caller-supplied bank identifiers.",
      "remediationTests": [
        "Reject binding without a verified bank session.",
        "Reject mismatched bank identity, staff identity, school, expired state, and replay."
      ],
      "rootCause": {
        "summary": "The unauthenticated controller accepts caller-selected bank and staff identity fields; matching staff attributes is followed by relation creation and token issuance."
      },
      "ruleId": "authentication.bank-identity-binding",
      "severity": {
        "changeConditions": "Lower if an independently verified gateway binds the request to the same bank identity; raise if directly Internet reachable.",
        "level": "high",
        "rationale": "The repository trace crosses a defined trust boundary and supports high impact with medium likelihood under the stated prerequisites."
      },
      "summary": "The bank identity-binding endpoint mints an application bearer token without proving control of a bank-authenticated identity.",
      "taxonomy": {
        "category": "Authentication bypass",
        "cwe": [
          "CWE-306",
          "CWE-862"
        ]
      },
      "title": "Bank identity binding mints a token without bank-session proof",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "An upstream bank gateway or reverse proxy could authenticate the path, but no such control appears in the repository."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "The unauthenticated controller accepts caller-selected bank and staff identity fields; matching staff attributes is followed by relation creation and token issuance."
      }
    },
    {
      "attackPath": {
        "dataflow": {
          "outcome": "Caller-supplied bank user identifiers allow linked-user enumeration and application token issuance.",
          "sink": "app/api/service/Bank.php",
          "source": "route/app.php",
          "summary": "public bank user ID -> linked-user enumeration -> caller-selected linked user -> application bearer token"
        },
        "impact": {
          "level": "high",
          "why": "Caller-supplied bank user identifiers allow linked-user enumeration and application token issuance."
        },
        "likelihood": {
          "level": "medium",
          "why": "An unauthenticated caller needs a bank user identifier; the first endpoint enumerates linked application users and the second returns a token."
        },
        "limitations": [
          "Bank-user identifier entropy and any upstream caller authentication are unknown."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": "route/app.php",
          "outcome": "Caller-supplied bank user identifiers allow linked-user enumeration and application token issuance.",
          "summary": "An unauthenticated caller needs a bank user identifier; the first endpoint enumerates linked application users and the second returns a token."
        },
        "summary": "An unauthenticated caller needs a bank user identifier; the first endpoint enumerates linked application users and the second returns a token."
      },
      "confidence": {
        "level": "high",
        "rationale": "Direct repository evidence establishes the attacker input, missing or broken control, and security-relevant sink."
      },
      "extensions": {
        "candidateId": "candidate-240f9187df004512"
      },
      "findingId": "csf_8b52eea445dd701a16360395",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:e5114fcb609f72ae4c2fbe78172b07f7444ac24c309449ba05add1e6c314bdcd"
      },
      "identity": {
        "anchor": "bank-user-id-not-bound-to-caller",
        "instance": "bank-user-id-account-switch"
      },
      "locations": [
        {
          "endLine": 26,
          "path": "route/app.php",
          "role": "entrypoint",
          "startLine": 25
        },
        {
          "endLine": 112,
          "path": "app/api/controller/Bank.php",
          "role": "source",
          "startLine": 81
        },
        {
          "endLine": 15,
          "path": "app/api/controller/Bank.php",
          "role": "root_control",
          "startLine": 15
        },
        {
          "endLine": 195,
          "path": "app/api/service/Bank.php",
          "role": "sink",
          "startLine": 105
        }
      ],
      "occurrenceId": "occ_7d62b823ead4c7b63b050ff3",
      "preventiveControls": [
        "Object ownership enforcement",
        "Opaque external identifiers",
        "Token-mint audit events"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Derive `bank_user_id` from the verified bank session, require the current session to own every relation, and prevent public linked-user enumeration.",
      "remediationTests": [
        "Reject related-user queries and account switches for a different bank identity.",
        "Confirm a verified owner can switch only to relations in the same school scope."
      ],
      "rootCause": {
        "summary": "Neither relatedUsers nor switchLoginUser proves caller ownership of bank_user_id before enumerating linked users and returning a bearer token."
      },
      "ruleId": "authorization.bank-account-switch",
      "severity": {
        "changeConditions": "Lower if identifiers are unguessable and gateway ownership is enforced; raise if identifiers are exposed through clients or logs.",
        "level": "high",
        "rationale": "The repository trace crosses a defined trust boundary and supports high impact with medium likelihood under the stated prerequisites."
      },
      "summary": "Caller-supplied bank user identifiers allow linked-user enumeration and application token issuance.",
      "taxonomy": {
        "category": "Authorization bypass / IDOR",
        "cwe": [
          "CWE-306",
          "CWE-639",
          "CWE-862"
        ]
      },
      "title": "Bank user ID permits linked-account enumeration and token issuance",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "Bank-user identifier entropy and any upstream caller authentication are unknown."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "Neither relatedUsers nor switchLoginUser proves caller ownership of bank_user_id before enumerating linked users and returning a bearer token."
      }
    },
    {
      "attackPath": {
        "dataflow": {
          "outcome": "The Baidu face API permits every caller when its IP allowlist is absent or empty.",
          "sink": "app/api/service/BaiduFaceOfflineService.php",
          "source": "route/app.php",
          "summary": "HTTP face route -> empty allowlist -> allow-all branch -> local biometric SDK operation -> biometric result or mutation"
        },
        "impact": {
          "level": "high",
          "why": "The Baidu face API permits every caller when its IP allowlist is absent or empty."
        },
        "likelihood": {
          "level": "medium",
          "why": "A network caller needs the API route to be reachable while allowed_ips is missing or empty; no login is required by repository controls."
        },
        "limitations": [
          "Production ingress and the deployed allowlist value were not inspected; a populated allowlist would reduce practical reachability."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": "route/app.php",
          "outcome": "The Baidu face API permits every caller when its IP allowlist is absent or empty.",
          "summary": "A network caller needs the API route to be reachable while allowed_ips is missing or empty; no login is required by repository controls."
        },
        "summary": "A network caller needs the API route to be reachable while allowed_ips is missing or empty; no login is required by repository controls."
      },
      "confidence": {
        "level": "high",
        "rationale": "Direct repository evidence establishes the attacker input, missing or broken control, and security-relevant sink."
      },
      "extensions": {
        "candidateId": "candidate-6e0e1345ea493eb6"
      },
      "findingId": "csf_56c76a090077e2b64d2ea558",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:00abcc4b81fb8ac5a5b422f875315b5ce81caa0b33f22cec1a5a79b25fc423ea"
      },
      "identity": {
        "anchor": "baidu-face-allowlist-empty-allows-all",
        "instance": "baidu-face-empty-allowlist"
      },
      "locations": [
        {
          "endLine": 99,
          "path": "route/app.php",
          "role": "entrypoint",
          "startLine": 86
        },
        {
          "endLine": 133,
          "path": "app/api/controller/BaiduFaceOffline.php",
          "role": "root_control",
          "startLine": 126
        },
        {
          "endLine": 7,
          "path": "config/baidu_face.php",
          "role": "root_control",
          "startLine": 7
        },
        {
          "endLine": 114,
          "path": "app/api/controller/BaiduFaceOffline.php",
          "role": "sink",
          "startLine": 93
        },
        {
          "endLine": 83,
          "path": "app/api/service/BaiduFaceOfflineService.php",
          "role": "concrete_implementation",
          "startLine": 21
        }
      ],
      "occurrenceId": "occ_389b6aa8655d97e7e36813a2",
      "preventiveControls": [
        "Fail-closed configuration validation",
        "Authenticated internal-service boundary",
        "Ingress restriction and audit logging"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Reject startup or every request when `allowed_ips` is empty; add authenticated device/service credentials and proxy-aware source validation.",
      "remediationTests": [
        "Assert an empty or missing allowlist returns 403 for every face operation.",
        "Assert an allowed source still requires a valid service or device credential."
      ],
      "rootCause": {
        "summary": "The checked-in default allowlist is empty and checkAllowedIp returns true in that state before sensitive biometric SDK operations."
      },
      "ruleId": "access-control.fail-open-allowlist",
      "severity": {
        "changeConditions": "Lower if production startup rejects an empty allowlist and ingress is independently restricted; raise if the route is publicly reachable with the default.",
        "level": "high",
        "rationale": "The repository trace crosses a defined trust boundary and supports high impact with medium likelihood under the stated prerequisites."
      },
      "summary": "The Baidu face API permits every caller when its IP allowlist is absent or empty.",
      "taxonomy": {
        "category": "Access-control fail-open",
        "cwe": [
          "CWE-284",
          "CWE-636"
        ]
      },
      "title": "Empty Baidu face allowlist fails open",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "Production ingress and the deployed allowlist value were not inspected; a populated allowlist would reduce practical reachability."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "The checked-in default allowlist is empty and checkAllowedIp returns true in that state before sensitive biometric SDK operations."
      }
    },
    {
      "attackPath": {
        "dataflow": {
          "outcome": "Huawei HealthKit access and refresh credentials are written to application logs.",
          "sink": "app/api/service/user/UserHealthKit.php",
          "source": "route/app.php",
          "summary": "OAuth callback -> vendor token response -> token array -> application log"
        },
        "impact": {
          "level": "high",
          "why": "Huawei HealthKit access and refresh credentials are written to application logs."
        },
        "likelihood": {
          "level": "high",
          "why": "A reader of application logs can recover reusable health credentials after a successful OAuth callback."
        },
        "limitations": [
          "Logger destination, access controls, retention, and token scope are deployment facts not available in the repository."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": "route/app.php",
          "outcome": "Huawei HealthKit access and refresh credentials are written to application logs.",
          "summary": "A reader of application logs can recover reusable health credentials after a successful OAuth callback."
        },
        "summary": "A reader of application logs can recover reusable health credentials after a successful OAuth callback."
      },
      "confidence": {
        "level": "high",
        "rationale": "Direct repository evidence establishes the attacker input, missing or broken control, and security-relevant sink."
      },
      "extensions": {
        "candidateId": "candidate-d5aca248fb7ca848"
      },
      "findingId": "csf_12f2a3bc6118b9a767dd9866",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:9499dae80a16fba0a6a34689b45b0e9d0e2924a6682f10d390128455fd9b6726"
      },
      "identity": {
        "anchor": "healthkit-token-array-logged",
        "instance": "healthkit-token-logging"
      },
      "locations": [
        {
          "endLine": 79,
          "path": "route/app.php",
          "role": "entrypoint",
          "startLine": 79
        },
        {
          "endLine": 91,
          "path": "app/api/controller/Healthkit.php",
          "role": "entrypoint/wrapper",
          "startLine": 68
        },
        {
          "endLine": 76,
          "path": "app/api/service/user/UserHealthKit.php",
          "role": "root_control",
          "startLine": 60
        }
      ],
      "occurrenceId": "occ_101bf9a10ed798d9f3aef0cf",
      "preventiveControls": [
        "Central logging redaction",
        "Sensitive-field schema annotations",
        "Log access and retention limits"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Never log token response objects; log only redacted metadata such as provider, user binding result, expiry, and a correlation ID, then rotate exposed tokens where possible.",
      "remediationTests": [
        "Capture application logs during OAuth exchange and assert no access or refresh token appears.",
        "Verify structured logger redaction covers nested credential fields."
      ],
      "rootCause": {
        "summary": "The token response is assembled and the complete token array is logged without redaction after OAuth exchange."
      },
      "ruleId": "secret-exposure.oauth-token-logging",
      "severity": {
        "changeConditions": "Lower if production logging redacts the array before storage; raise if logs are broadly accessible or tokens have broad/long-lived scope.",
        "level": "high",
        "rationale": "The repository trace crosses a defined trust boundary and supports high impact with high likelihood under the stated prerequisites."
      },
      "summary": "Huawei HealthKit access and refresh credentials are written to application logs.",
      "taxonomy": {
        "category": "Sensitive information in logs",
        "cwe": [
          "CWE-532"
        ]
      },
      "title": "HealthKit access and refresh tokens are logged",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "Logger destination, access controls, retention, and token scope are deployment facts not available in the repository."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "The token response is assembled and the complete token array is logged without redaction after OAuth exchange."
      }
    },
    {
      "attackPath": {
        "dataflow": {
          "outcome": "Tracked environment files and a database dump contain non-placeholder credential assignments.",
          "sink": ".env.prodaizhct_sanquan",
          "source": ".env.dev",
          "summary": "tracked environment or SQL file -> repository clone/checkout -> credential disclosure to any repository reader"
        },
        "impact": {
          "level": "high",
          "why": "Tracked environment files and a database dump contain non-placeholder credential assignments."
        },
        "likelihood": {
          "level": "high",
          "why": "Any person or automation able to read the Codeup repository, local clone, archive, or derived artifact can obtain the committed material."
        },
        "limitations": [
          "Credential values were deliberately not reproduced or tested, so current validity and privilege are unknown."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": ".env.dev",
          "outcome": "Tracked environment files and a database dump contain non-placeholder credential assignments.",
          "summary": "Any person or automation able to read the Codeup repository, local clone, archive, or derived artifact can obtain the committed material."
        },
        "summary": "Any person or automation able to read the Codeup repository, local clone, archive, or derived artifact can obtain the committed material."
      },
      "confidence": {
        "level": "high",
        "rationale": "Tracked files directly contain non-placeholder credential assignments, but the values were not tested for validity."
      },
      "extensions": {
        "candidateId": "candidate-1fe163facac3c106"
      },
      "findingId": "csf_37a78fc3c444c7454dafd658",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:5e08d43dc87225b67d092dd6dd23f48ad0e66372fd3b7c4f94022da8f9b6f009"
      },
      "identity": {
        "anchor": "tracked-runtime-credentials",
        "instance": "tracked-environment-and-sql-credentials"
      },
      "locations": [
        {
          "endLine": 1,
          "path": ".env.dev",
          "role": "root_control",
          "startLine": 1
        },
        {
          "endLine": 1,
          "path": ".env.prod",
          "role": "root_control",
          "startLine": 1
        },
        {
          "endLine": 50822,
          "path": "db/aizhct_jx206.sql",
          "role": "sink",
          "startLine": 50822
        },
        {
          "endLine": 1,
          "path": ".env.prodaizhct_cqhw",
          "role": "evidence",
          "startLine": 1
        },
        {
          "endLine": 1,
          "path": ".env.prodaizhct_rdfz",
          "role": "evidence",
          "startLine": 1
        },
        {
          "endLine": 1,
          "path": ".env.prodaizhct_sanquan",
          "role": "evidence",
          "startLine": 1
        }
      ],
      "occurrenceId": "occ_6bc686f289a3366647c3ff46",
      "preventiveControls": [
        "Codeup secret scanning",
        "Pre-commit secret detection",
        "Central secret rotation and inventory"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Rotate every potentially exposed credential, remove values from the current tree and history where authorized, store only templates, and inject secrets through the deployment secret store.",
      "remediationTests": [
        "Run a secret scanner against the full Git history and fail on non-placeholder values.",
        "Verify rotated credentials are rejected and new credentials are available only at runtime."
      ],
      "rootCause": {
        "summary": "Eighteen tracked environment variants contain sensitive-named non-placeholder assignments, and a tracked SQL dump includes an access-key identifier and secret. Values were not copied into artifacts or tested."
      },
      "ruleId": "hardcoded-credentials.repository-artifacts",
      "severity": {
        "changeConditions": "Lower after all values are proven inactive and rotated; raise if any credential is active with production or cross-tenant privilege.",
        "level": "high",
        "rationale": "The repository trace crosses a defined trust boundary and supports high impact with high likelihood under the stated prerequisites."
      },
      "summary": "Tracked environment files and a database dump contain non-placeholder credential assignments.",
      "taxonomy": {
        "category": "Hardcoded credentials",
        "cwe": [
          "CWE-312",
          "CWE-798"
        ]
      },
      "title": "Repository contains environment and SQL credential material",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "Credential values were deliberately not reproduced or tested, so current validity and privilege are unknown."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "Eighteen tracked environment variants contain sensitive-named non-placeholder assignments, and a tracked SQL dump includes an access-key identifier and secret. Values were not copied into artifacts or tested."
      }
    },
    {
      "attackPath": {
        "dataflow": {
          "outcome": "Terminal face synchronization is whitelisted from login and discloses biometric synchronization data using identifiers rather than device authentication.",
          "sink": "app/terminal/service/staff/Face.php",
          "source": "app/terminal/controller/store/Face.php",
          "summary": "whitelisted terminal request -> caller-supplied device/store identifiers -> face sync query -> biometric metadata response and sync-state update"
        },
        "impact": {
          "level": "high",
          "why": "Terminal face synchronization is whitelisted from login and discloses biometric synchronization data using identifiers rather than device authentication."
        },
        "likelihood": {
          "level": "medium",
          "why": "A caller needs a device identifier and matching store context; repository code requires no device credential or request signature."
        },
        "limitations": [
          "Device-code entropy, network placement, and external gateway controls are unknown."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": "app/terminal/controller/store/Face.php",
          "outcome": "Terminal face synchronization is whitelisted from login and discloses biometric synchronization data using identifiers rather than device authentication.",
          "summary": "A caller needs a device identifier and matching store context; repository code requires no device credential or request signature."
        },
        "summary": "A caller needs a device identifier and matching store context; repository code requires no device credential or request signature."
      },
      "confidence": {
        "level": "high",
        "rationale": "Direct repository evidence establishes the attacker input, missing or broken control, and security-relevant sink."
      },
      "extensions": {
        "candidateId": "candidate-63b43fe82deb5555"
      },
      "findingId": "csf_c4d99c0c4dad8cb2e6d96bb1",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:229f7a744c2e5c9b7a2c24c7212fdf29ad96c620c356d00a43ba57dc19e06e82"
      },
      "identity": {
        "anchor": "terminal-face-whitelist-without-device-credential",
        "instance": "terminal-face-sync-without-device-auth"
      },
      "locations": [
        {
          "endLine": 70,
          "path": "app/terminal/controller/store/Face.php",
          "role": "entrypoint",
          "startLine": 51
        },
        {
          "endLine": 58,
          "path": "app/terminal/controller/Controller.php",
          "role": "root_control",
          "startLine": 42
        },
        {
          "endLine": 153,
          "path": "app/terminal/controller/Controller.php",
          "role": "root_control",
          "startLine": 147
        },
        {
          "endLine": 180,
          "path": "app/terminal/service/staff/Face.php",
          "role": "sink",
          "startLine": 99
        }
      ],
      "occurrenceId": "occ_ed31674f4bf15780bbb8d8fe",
      "preventiveControls": [
        "Per-device credentials",
        "Request signing and replay protection",
        "Biometric access audit trail"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Remove face synchronization from the anonymous whitelist and require a per-device credential or signed request bound to store, timestamp, nonce, and operation.",
      "remediationTests": [
        "Reject face sync and feedback without a valid device credential.",
        "Reject replay, cross-store device IDs, and stale signatures."
      ],
      "rootCause": {
        "summary": "The route bypasses login; service checks pair/store identifiers but no credential or signature before returning names, image URLs, and face tokens and updating sync state."
      },
      "ruleId": "device-authentication.face-sync",
      "severity": {
        "changeConditions": "Lower if the endpoint is mutually authenticated at ingress; raise if device IDs are enumerable or exposed.",
        "level": "high",
        "rationale": "The repository trace crosses a defined trust boundary and supports high impact with medium likelihood under the stated prerequisites."
      },
      "summary": "Terminal face synchronization is whitelisted from login and discloses biometric synchronization data using identifiers rather than device authentication.",
      "taxonomy": {
        "category": "Missing device authentication",
        "cwe": [
          "CWE-200",
          "CWE-306",
          "CWE-862"
        ]
      },
      "title": "Terminal face sync exposes biometric data without device authentication",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "Device-code entropy, network placement, and external gateway controls are unknown."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "The route bypasses login; service checks pair/store identifiers but no credential or signature before returning names, image URLs, and face tokens and updating sync state."
      }
    },
    {
      "attackPath": {
        "dataflow": {
          "outcome": "Terminal role and API privilege enforcement is entirely commented out.",
          "sink": "app/terminal/controller/store/User.php",
          "source": "app/terminal/controller/Controller.php",
          "summary": "low-privilege terminal login -> automatic controller route -> no-op privilege check -> administrator or refund mutation"
        },
        "impact": {
          "level": "high",
          "why": "Terminal role and API privilege enforcement is entirely commented out."
        },
        "likelihood": {
          "level": "high",
          "why": "Any authenticated terminal merchant user can call role-restricted endpoints directly because the permission decision is disabled."
        },
        "limitations": [
          "No external per-route authorization layer is represented; if one exists it could narrow the path."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": "app/terminal/controller/Controller.php",
          "outcome": "Terminal role and API privilege enforcement is entirely commented out.",
          "summary": "Any authenticated terminal merchant user can call role-restricted endpoints directly because the permission decision is disabled."
        },
        "summary": "Any authenticated terminal merchant user can call role-restricted endpoints directly because the permission decision is disabled."
      },
      "confidence": {
        "level": "high",
        "rationale": "Direct repository evidence establishes the attacker input, missing or broken control, and security-relevant sink."
      },
      "extensions": {
        "candidateId": "candidate-5a8c091052c7bc27"
      },
      "findingId": "csf_854ff8ffd6d76a8354a5b54a",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:174ad839c65ca22c05483547cb8694659eb07a1cd89cb39b2dd98e95b060e0db"
      },
      "identity": {
        "anchor": "terminal-check-privilege-no-op",
        "instance": "terminal-role-authorization-disabled"
      },
      "locations": [
        {
          "endLine": 92,
          "path": "app/terminal/controller/Controller.php",
          "role": "entrypoint",
          "startLine": 74
        },
        {
          "endLine": 116,
          "path": "app/terminal/controller/Controller.php",
          "role": "root_control",
          "startLine": 110
        },
        {
          "endLine": 77,
          "path": "app/terminal/controller/order/Refund.php",
          "role": "sink",
          "startLine": 53
        },
        {
          "endLine": 109,
          "path": "app/terminal/controller/store/User.php",
          "role": "sink",
          "startLine": 71
        }
      ],
      "occurrenceId": "occ_60ff0b14aed91f05320376cb",
      "preventiveControls": [
        "Central deny-by-default authorization",
        "Role-to-route policy tests",
        "Privileged-action audit logging"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Restore fail-closed route authorization using the same permission model as the store application, then explicitly register every terminal API and role.",
      "remediationTests": [
        "Assert low-privilege users cannot manage administrators, payment settings, or refunds.",
        "Assert unknown or unregistered terminal routes are denied."
      ],
      "rootCause": {
        "summary": "Initialization calls checkPrivilege, but that method performs no decision; any logged-in terminal merchant can reach administrator and refund mutations."
      },
      "ruleId": "authorization.terminal-role-check",
      "severity": {
        "changeConditions": "Lower only if an external authorizer enforces the same route-level permissions; raise if low-privilege terminal accounts are broadly issued.",
        "level": "high",
        "rationale": "The repository trace crosses a defined trust boundary and supports high impact with high likelihood under the stated prerequisites."
      },
      "summary": "Terminal role and API privilege enforcement is entirely commented out.",
      "taxonomy": {
        "category": "Missing function-level authorization",
        "cwe": [
          "CWE-862"
        ]
      },
      "title": "Terminal role authorization is disabled",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "No external per-route authorization layer is represented; if one exists it could narrow the path."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "Initialization calls checkPrivilege, but that method performs no decision; any logged-in terminal merchant can reach administrator and refund mutations."
      }
    },
    {
      "attackPath": {
        "dataflow": {
          "outcome": "The Zhaiker callback accepts unsigned caller-controlled health records for an arbitrary numeric user ID.",
          "sink": "app/api/service/user/UserWeight.php",
          "source": "route/app.php",
          "summary": "unsigned HTTP payload -> callback normalization -> attacker-selected customerId -> measurement and health writes -> weight/profile synchronization"
        },
        "impact": {
          "level": "high",
          "why": "The Zhaiker callback accepts unsigned caller-controlled health records for an arbitrary numeric user ID."
        },
        "likelihood": {
          "level": "medium",
          "why": "A network caller that reaches the callback can submit an arbitrary numeric user ID; an existing ID expands impact to profile and reward mutation."
        },
        "limitations": [
          "Production ingress and any upstream callback authentication were not inspected."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": "route/app.php",
          "outcome": "The Zhaiker callback accepts unsigned caller-controlled health records for an arbitrary numeric user ID.",
          "summary": "A network caller that reaches the callback can submit an arbitrary numeric user ID; an existing ID expands impact to profile and reward mutation."
        },
        "summary": "A network caller that reaches the callback can submit an arbitrary numeric user ID; an existing ID expands impact to profile and reward mutation."
      },
      "confidence": {
        "level": "high",
        "rationale": "Direct repository evidence establishes the attacker input, missing or broken control, and security-relevant sink."
      },
      "extensions": {
        "candidateId": "candidate-2d26714434a20001"
      },
      "findingId": "csf_cfc725b1aa3de712de05af0d",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:7190eb92eda8990b3bd392e5a8ca32e77257932b0a817c2906c7471ffed086d5"
      },
      "identity": {
        "anchor": "zhaiker-callback-without-authenticity",
        "instance": "zhaiker-forged-exam-callback"
      },
      "locations": [
        {
          "endLine": 68,
          "path": "route/app.php",
          "role": "entrypoint",
          "startLine": 68
        },
        {
          "endLine": 54,
          "path": "app/api/service/ZhaikerExamImporter.php",
          "role": "source",
          "startLine": 38
        },
        {
          "endLine": 36,
          "path": "app/api/controller/zhaiker/Callback.php",
          "role": "root_control",
          "startLine": 12
        },
        {
          "endLine": 100,
          "path": "app/api/service/ZhaikerExamImporter.php",
          "role": "sink",
          "startLine": 85
        },
        {
          "endLine": 221,
          "path": "app/api/service/ZhaikerExamImporter.php",
          "role": "sink",
          "startLine": 211
        },
        {
          "endLine": 294,
          "path": "app/api/service/user/UserWeight.php",
          "role": "concrete_implementation",
          "startLine": 163
        }
      ],
      "occurrenceId": "occ_cf4ede526278c9aa00e82058",
      "preventiveControls": [
        "Signed callback middleware",
        "Nonce replay store",
        "Vendor-to-user binding invariant"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Require a vendor signature over the raw body plus timestamp and nonce, reject replays, and bind the vendor subject to the expected user before writes.",
      "remediationTests": [
        "Reject missing, invalid, expired, and replayed callback signatures.",
        "Reject a valid vendor callback whose subject is not bound to the target user."
      ],
      "rootCause": {
        "summary": "No signature, secret, timestamp, nonce, source allowlist, login, or vendor-to-user binding precedes durable health and weight writes."
      },
      "ruleId": "callback-authenticity.health-import",
      "severity": {
        "changeConditions": "Lower if a verified upstream gateway authenticates and signs every callback; raise if the route is Internet reachable.",
        "level": "high",
        "rationale": "The repository trace crosses a defined trust boundary and supports high impact with medium likelihood under the stated prerequisites."
      },
      "summary": "The Zhaiker callback accepts unsigned caller-controlled health records for an arbitrary numeric user ID.",
      "taxonomy": {
        "category": "Missing callback authentication",
        "cwe": [
          "CWE-306",
          "CWE-345",
          "CWE-862"
        ]
      },
      "title": "Unsigned Zhaiker callback can forge user health records",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "Production ingress and any upstream callback authentication were not inspected."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "No signature, secret, timestamp, nonce, source allowlist, login, or vendor-to-user binding precedes durable health and weight writes."
      }
    },
    {
      "attackPath": {
        "dataflow": {
          "outcome": "Concurrent prepay scans can both create meal orders while only one links to the prepay row.",
          "sink": "app/api/service/PrepayOrder.php",
          "source": "route/app.php",
          "summary": "concurrent authenticated scans -> unlocked prepay read -> duplicate order inserts -> one failed link update ignored -> orphan payable order"
        },
        "impact": {
          "level": "medium",
          "why": "Concurrent prepay scans can both create meal orders while only one links to the prepay row."
        },
        "likelihood": {
          "level": "medium",
          "why": "An authenticated user can replay or race the scan request for one waiting prepay row."
        },
        "limitations": [
          "Unrepresented database triggers or constraints could alter the concurrent outcome."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": "route/app.php",
          "outcome": "Concurrent prepay scans can both create meal orders while only one links to the prepay row.",
          "summary": "An authenticated user can replay or race the scan request for one waiting prepay row."
        },
        "summary": "An authenticated user can replay or race the scan request for one waiting prepay row."
      },
      "confidence": {
        "level": "high",
        "rationale": "Direct repository evidence establishes the attacker input, missing or broken control, and security-relevant sink."
      },
      "extensions": {
        "candidateId": "candidate-8d7ad69adfd638b8"
      },
      "findingId": "csf_56976f6f3a2d9b7cf84d3a28",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:64377dd5def453376276adb752f27192010510c45a0ec2fa21b292e9b729f7b3"
      },
      "identity": {
        "anchor": "prepay-link-check-before-lock",
        "instance": "prepay-concurrent-orphan-orders"
      },
      "locations": [
        {
          "endLine": 147,
          "path": "route/app.php",
          "role": "entrypoint",
          "startLine": 145
        },
        {
          "endLine": 17,
          "path": "app/api/controller/PrepayOrder.php",
          "role": "entrypoint/wrapper",
          "startLine": 14
        },
        {
          "endLine": 69,
          "path": "app/api/service/PrepayOrder.php",
          "role": "root_control",
          "startLine": 23
        },
        {
          "endLine": 126,
          "path": "app/api/service/PrepayOrder.php",
          "role": "sink",
          "startLine": 81
        }
      ],
      "occurrenceId": "occ_95c8cd7cb1f916589b69414c",
      "preventiveControls": [
        "Database uniqueness and row locking",
        "Idempotency keys",
        "Affected-row assertions"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Lock the prepay row inside the transaction or atomically claim it before creating the meal order; verify affected rows and roll back the losing request.",
      "remediationTests": [
        "Issue parallel scans for the same prepay row and assert exactly one meal order is committed.",
        "Assert the losing request returns the already-linked order or an idempotent conflict."
      ],
      "rootCause": {
        "summary": "The row is read without a lock before the transaction; both requests can insert, and the conditional link update's affected-row count is not checked before commit and return."
      },
      "ruleId": "race-condition.prepay-order",
      "severity": {
        "changeConditions": "Raise if duplicate payable orders can cause repeated settlement or fulfillment; lower if a database constraint atomically rejects and rolls back the losing insert.",
        "level": "medium",
        "rationale": "The repository trace crosses a defined trust boundary and supports medium impact with medium likelihood under the stated prerequisites."
      },
      "summary": "Concurrent prepay scans can both create meal orders while only one links to the prepay row.",
      "taxonomy": {
        "category": "Race condition / missing idempotency",
        "cwe": [
          "CWE-362",
          "CWE-367"
        ]
      },
      "title": "Concurrent prepay scans can create duplicate orphan orders",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "Unrepresented database triggers or constraints could alter the concurrent outcome."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "The row is read without a lock before the transaction; both requests can insert, and the conditional link update's affected-row count is not checked before commit and return."
      }
    },
    {
      "attackPath": {
        "dataflow": {
          "outcome": "HealthKit OAuth state is caller-selected, unbound to a session, and not single-use.",
          "sink": "app/api/service/user/UserHealthKit.php",
          "source": "route/app.php",
          "summary": "attacker-selected OAuth state/code -> logged-in callback -> syntactic state check only -> token stored under victim application account"
        },
        "impact": {
          "level": "medium",
          "why": "HealthKit OAuth state is caller-selected, unbound to a session, and not single-use."
        },
        "likelihood": {
          "level": "medium",
          "why": "An attacker must induce a logged-in victim to complete a callback using an attacker-originated Huawei authorization."
        },
        "limitations": [
          "A live OAuth/browser reproduction was not run; redirect-code constraints can reduce replay but do not replace state binding."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": "route/app.php",
          "outcome": "HealthKit OAuth state is caller-selected, unbound to a session, and not single-use.",
          "summary": "An attacker must induce a logged-in victim to complete a callback using an attacker-originated Huawei authorization."
        },
        "summary": "An attacker must induce a logged-in victim to complete a callback using an attacker-originated Huawei authorization."
      },
      "confidence": {
        "level": "medium",
        "rationale": "The missing server-side state lifecycle is source-proven; practical login-CSRF delivery remains untested."
      },
      "extensions": {
        "candidateId": "candidate-c6ec4b0f980d4e56"
      },
      "findingId": "csf_a6374d43c6eda5040043a45f",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:096955932e9afd74bec9ef3d1acf4ab465d3d9c837d76d8432caf00b14f1090b"
      },
      "identity": {
        "anchor": "healthkit-state-syntax-only-validation",
        "instance": "healthkit-oauth-state-unbound"
      },
      "locations": [
        {
          "endLine": 79,
          "path": "route/app.php",
          "role": "entrypoint",
          "startLine": 79
        },
        {
          "endLine": 190,
          "path": "app/api/controller/Healthkit.php",
          "role": "root_control",
          "startLine": 147
        },
        {
          "endLine": 91,
          "path": "app/api/controller/Healthkit.php",
          "role": "sink",
          "startLine": 68
        },
        {
          "endLine": 76,
          "path": "app/api/service/user/UserHealthKit.php",
          "role": "concrete_implementation",
          "startLine": 60
        }
      ],
      "occurrenceId": "occ_9cf11255b2415e83e6765037",
      "preventiveControls": [
        "Server-side OAuth transaction store",
        "Atomic state consumption",
        "Provider-account binding audit"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Generate high-entropy state server-side, bind it to the initiating application user and redirect, enforce short expiry and single use, and consume it atomically before token exchange.",
      "remediationTests": [
        "Reject unknown, expired, reused, or other-user state values.",
        "Complete one authorized callback and verify the same state cannot be consumed again."
      ],
      "rootCause": {
        "summary": "Any syntactically matching state or JSON object with a nonce is accepted without server-side issuance, expiry, session binding, or consumption before storing vendor tokens for the logged-in user."
      },
      "ruleId": "oauth.state-binding",
      "severity": {
        "changeConditions": "Raise with a browser PoC demonstrating account misbinding; lower if a verified external component issues and consumes a session-bound state before this controller.",
        "level": "medium",
        "rationale": "The repository trace crosses a defined trust boundary and supports medium impact with medium likelihood under the stated prerequisites."
      },
      "summary": "HealthKit OAuth state is caller-selected, unbound to a session, and not single-use.",
      "taxonomy": {
        "category": "OAuth login CSRF / account misbinding",
        "cwe": [
          "CWE-345",
          "CWE-352"
        ]
      },
      "title": "HealthKit OAuth state is not session-bound or single-use",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "A live OAuth/browser reproduction was not run; redirect-code constraints can reduce replay but do not replace state binding."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "Any syntactically matching state or JSON object with a nonce is accepted without server-side issuance, expiry, session binding, or consumption before storing vendor tokens for the logged-in user."
      }
    },
    {
      "attackPath": {
        "dataflow": {
          "outcome": "Omitting a request flag disables login for the image-upload endpoint.",
          "sink": "app/common/library/storage/FileValidate.php",
          "source": "app/api/controller/Upload.php",
          "summary": "anonymous automatic route -> omitted flag -> login disabled -> file storage and metadata write"
        },
        "impact": {
          "level": "medium",
          "why": "Omitting a request flag disables login for the image-upload endpoint."
        },
        "likelihood": {
          "level": "high",
          "why": "Any network caller that can reach the API automatic route can upload repeated extension-approved files without a login."
        },
        "limitations": [
          "External WAF, rate limits, storage ACLs, and product intent for anonymous uploads are unknown."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": "app/api/controller/Upload.php",
          "outcome": "Omitting a request flag disables login for the image-upload endpoint.",
          "summary": "Any network caller that can reach the API automatic route can upload repeated extension-approved files without a login."
        },
        "summary": "Any network caller that can reach the API automatic route can upload repeated extension-approved files without a login."
      },
      "confidence": {
        "level": "medium",
        "rationale": "The default unauthenticated storage path is source-proven, while deployment quotas and intended product behavior are unknown."
      },
      "extensions": {
        "candidateId": "candidate-ad924a1115f04ffe"
      },
      "findingId": "csf_acb53b2eeb75f430fdb8bc91",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:925a8c562907f61a953a08d8fd0cb9e4f6c0c8af6fe5b48a9309a2ce47d3f3bf"
      },
      "identity": {
        "anchor": "upload-empty-flag-disables-login",
        "instance": "anonymous-image-upload"
      },
      "locations": [
        {
          "endLine": 38,
          "path": "app/api/controller/Upload.php",
          "role": "root_control",
          "startLine": 30
        },
        {
          "endLine": 63,
          "path": "app/api/service/Upload.php",
          "role": "sink",
          "startLine": 50
        },
        {
          "endLine": 24,
          "path": "app/common/library/storage/FileValidate.php",
          "role": "concrete_implementation",
          "startLine": 17
        }
      ],
      "occurrenceId": "occ_dc67cebd1c27baf96d490955",
      "preventiveControls": [
        "Authentication middleware",
        "Upload quotas and throttling",
        "Content re-encoding and malware scanning"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Require authentication by default, remove request-controlled authentication toggles, enforce decoded image MIME validation, and apply tenant/user quotas and rate limits.",
      "remediationTests": [
        "Reject uploads without a valid access token regardless of `flag`.",
        "Enforce file type, per-user quota, and request-rate limits."
      ],
      "rootCause": {
        "summary": "The controller forces checkLogin false for the default request; the service writes storage and metadata for uploader 0 with per-file extension and size checks but no path-local quota or rate limit."
      },
      "ruleId": "resource-abuse.anonymous-upload",
      "severity": {
        "changeConditions": "Raise if public storage is billable/executable or has no global quota; lower if anonymous upload is intentional and protected by strict gateway quotas.",
        "level": "medium",
        "rationale": "The repository trace crosses a defined trust boundary and supports medium impact with high likelihood under the stated prerequisites."
      },
      "summary": "Omitting a request flag disables login for the image-upload endpoint.",
      "taxonomy": {
        "category": "Unauthenticated resource consumption",
        "cwe": [
          "CWE-400",
          "CWE-862"
        ]
      },
      "title": "Image upload disables authentication when flag is omitted",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "External WAF, rate limits, storage ACLs, and product intent for anonymous uploads are unknown."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "The controller forces checkLogin false for the default request; the service writes storage and metadata for uploader 0 with per-file extension and size checks but no path-local quota or rate limit."
      }
    },
    {
      "attackPath": {
        "dataflow": {
          "outcome": "Refund application accepts a same-store order-goods identifier without binding it to the current user.",
          "sink": "app/store/model/OrderRefund.php",
          "source": "app/api/controller/Refund.php",
          "summary": "same-store orderGoodsId -> ownerless goods lookup -> attacker refund row -> merchant approval path -> victim order refund"
        },
        "impact": {
          "level": "medium",
          "why": "Refund application accepts a same-store order-goods identifier without binding it to the current user."
        },
        "likelihood": {
          "level": "medium",
          "why": "An ordinary API user needs a same-store order-goods ID; final payment reversal additionally requires merchant approval."
        },
        "limitations": [
          "Final payment reversal requires merchant approval; live database reproduction was not attempted."
        ],
        "reachability": {
          "attacker": "network caller, repository reader, device caller, or lower-privilege authenticated user as described",
          "entrypoint": "app/api/controller/Refund.php",
          "outcome": "Refund application accepts a same-store order-goods identifier without binding it to the current user.",
          "summary": "An ordinary API user needs a same-store order-goods ID; final payment reversal additionally requires merchant approval."
        },
        "summary": "An ordinary API user needs a same-store order-goods ID; final payment reversal additionally requires merchant approval."
      },
      "confidence": {
        "level": "high",
        "rationale": "Direct repository evidence establishes the attacker input, missing or broken control, and security-relevant sink."
      },
      "extensions": {
        "candidateId": "candidate-e589ab1543013d97"
      },
      "findingId": "csf_cb9b93a3db8581ef53001ebe",
      "fingerprints": {
        "algorithm": "codex-security/v1",
        "primary": "codex-security/v1:sha256:f3b91aa56ee14782605ff00ff0282a4fa940392575105d60c64eb0c6a6a2046c"
      },
      "identity": {
        "anchor": "refund-goods-lookup-without-owner",
        "instance": "refund-order-goods-horizontal-bypass"
      },
      "locations": [
        {
          "endLine": 56,
          "path": "app/api/controller/Refund.php",
          "role": "source",
          "startLine": 39
        },
        {
          "endLine": 43,
          "path": "app/api/controller/Controller.php",
          "role": "root_control",
          "startLine": 35
        },
        {
          "endLine": 143,
          "path": "app/api/model/OrderRefund.php",
          "role": "root_control",
          "startLine": 137
        },
        {
          "endLine": 199,
          "path": "app/api/model/OrderRefund.php",
          "role": "sink",
          "startLine": 184
        },
        {
          "endLine": 160,
          "path": "app/store/model/OrderRefund.php",
          "role": "concrete_implementation",
          "startLine": 137
        }
      ],
      "occurrenceId": "occ_f97718ab8de37b4e13031ead",
      "preventiveControls": [
        "Owner-scoped repository methods",
        "Authorization tests for every object ID",
        "Refund workflow anomaly detection"
      ],
      "provenance": {
        "source": "local_plugin"
      },
      "remediation": "Resolve `orderGoodsId` through the current authenticated user's order relationship and reject any owner mismatch before disclosure or refund-row creation.",
      "remediationTests": [
        "Reject reading or refunding another user's same-store order goods.",
        "Confirm merchant approval cannot override a missing original-owner binding."
      ],
      "rootCause": {
        "summary": "The selected goods are scoped to store but not owner; a new refund row references the victim order and can later drive merchant-approved refund processing."
      },
      "ruleId": "authorization.refund-order-object",
      "severity": {
        "changeConditions": "Raise if approval is automatic or attacker-controlled; lower if merchant review reliably verifies ownership before any refund.",
        "level": "medium",
        "rationale": "The repository trace crosses a defined trust boundary and supports medium impact with medium likelihood under the stated prerequisites."
      },
      "summary": "Refund application accepts a same-store order-goods identifier without binding it to the current user.",
      "taxonomy": {
        "category": "Authorization bypass / IDOR",
        "cwe": [
          "CWE-639",
          "CWE-862"
        ]
      },
      "title": "Refund request is not bound to the current order owner",
      "validation": {
        "assertions": [
          "Attacker-controlled input or lower-trust boundary identified.",
          "Closest authentication, authorization, authenticity, or safety control inspected.",
          "Security-relevant sink or durable state transition traced.",
          "Repository counterevidence and deployment assumptions checked.",
          "Impact and remaining proof gap stated without claiming a live exploit."
        ],
        "limitations": [
          "Final payment reversal requires merchant approval; live database reproduction was not attempted."
        ],
        "method": "large internal repository static source-control-sink trace",
        "summary": "The selected goods are scoped to store but not owner; a new refund row references the victim order and can later drive merchant-approved refund processing."
      }
    }
  ],
  "scanId": "45fc7c79-225a-4c49-a5d0-2d456346a5f5",
  "schemaVersion": "1.0"
}
