From 70890bb900d4d5a18591dea3d54691c18dbc0655 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 22 Sep 2026 19:44:44 +0200 Subject: [PATCH] :bug: Store audit initiator as plain string for shared-key callers (#11629) Shared-key callers (exporter, admin-console) arrive as keywords on auth-key-id, so transit persisted them as ~:exporter while regular traffic stored app. Coerce with d/name at the single origin so every audit and telemetry copy carries a plain string. Adds regression tests for the origin and the push-audit-events path, including caller spoofing precedence. Closes #11628 AI-assisted-by: muse-spark-1.3-contributor --- backend/src/app/loggers/audit.clj | 2 +- backend/test/backend_tests/rpc_audit_test.clj | 15 ++++++++++++ .../rpc_management_nitrate_test.clj | 23 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index 6ded7befaf..28d14c3daa 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -204,7 +204,7 @@ token-id (::actoken/id request) token-type (::actoken/type request)] {:external-session-id session-id - :initiator (or key-id "app") + :initiator (or (d/name key-id) "app") :access-token-id (some-> token-id str) :access-token-type (some-> token-type str) :client-event-origin client-event-origin diff --git a/backend/test/backend_tests/rpc_audit_test.clj b/backend/test/backend_tests/rpc_audit_test.clj index f4cb76f2a3..6148df5831 100644 --- a/backend/test/backend_tests/rpc_audit_test.clj +++ b/backend/test/backend_tests/rpc_audit_test.clj @@ -35,6 +35,21 @@ "x-forwarded-for" "127.0.0.44" "x-real-ip" "127.0.0.43")))) +(t/deftest prepare-context-initiator-is-plain-string + ;; The initiator must always be a plain string, never a keyword: shared-key + ;; authenticated callers (exporter, admin-console) arrive as keywords on + ;; :app.http/auth-key-id and transit would persist them as "~:exporter". + (let [base {:headers {"x-forwarded-for" "127.0.0.44"}}] + (t/is (= "app" (:initiator (audit/prepare-context-from-request base)))) + (t/is (= "exporter" + (:initiator (audit/prepare-context-from-request + (assoc base :app.http/auth-key-id :exporter))))) + (t/is (= "admin-console" + (:initiator (audit/prepare-context-from-request + (assoc base :app.http/auth-key-id :admin-console))))) + (t/is (string? (:initiator (audit/prepare-context-from-request + (assoc base :app.http/auth-key-id :nexus))))))) + (t/deftest push-events-1 (with-redefs [app.config/flags #{:audit-log}] (let [prof (th/create-profile* 1 {:is-active true}) diff --git a/backend/test/backend_tests/rpc_management_nitrate_test.clj b/backend/test/backend_tests/rpc_management_nitrate_test.clj index 1589026db1..3d1d884abe 100644 --- a/backend/test/backend_tests/rpc_management_nitrate_test.clj +++ b/backend/test/backend_tests/rpc_management_nitrate_test.clj @@ -1943,3 +1943,26 @@ (t/is (= "bar" (get-in event [:context :foo]))) (t/is (= (:full cf/version) (get-in event [:context :version]))) (t/is (= "app" (get-in event [:context :initiator])))))))) + +(t/deftest push-audit-events-initiator-is-plain-string + ;; Shared-key callers (e.g. admin-console) carry :app.http/auth-key-id as a + ;; keyword; the stored initiator must be a plain string, and a + ;; caller-supplied initiator must never survive (server context wins). + (with-mocks [audit-mock {:target 'app.loggers.audit/submit :return nil}] + (binding [cf/flags #{:audit-log}] + (let [prof (th/create-profile* 1 {:is-active true}) + params {::th/type :push-audit-events + :events [{:name "context-test" + :profile-id (:id prof) + :type "action" + :context {:custom-key "custom-val" + :initiator "spoofed"}}]} + params (with-meta params + {::http/request (assoc http-request + ::http/auth-key-id :admin-console)}) + out (th/management-command! params)] + (t/is (nil? (:error out))) + (let [[_ event] (:call-args @audit-mock)] + (t/is (= "custom-val" (get-in event [:context :custom-key]))) + (t/is (= "admin-console" (get-in event [:context :initiator]))) + (t/is (string? (get-in event [:context :initiator]))))))))