惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
B
Blog
Jina AI
Jina AI
N
Netflix TechBlog - Medium
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园_首页
Hugging Face - Blog
Hugging Face - Blog
博客园 - 聂微东
美团技术团队
Google DeepMind News
Google DeepMind News
WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志
U
Unit 42
The Cloudflare Blog
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
小众软件
小众软件
罗磊的独立博客
Microsoft Security Blog
Microsoft Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
I
InfoQ
GbyAI
GbyAI
腾讯CDC
MongoDB | Blog
MongoDB | Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
The Splunk Token That Silently Swallows Curly Braces
Chris Ray · 2026-06-02 · via DEV Community

Your Okta detection fires. A privilege grant, the kind you want eyes on fast. The Slack alert message lands and tells you the actor "" just handed out admin. Two empty quotes where the name should be.

The search is correct. The field is populated. Run the SPL by hand and you'll see the display name sitting right there in the results. But the alert went out with a blank, and Splunk never said a word about it – not in the job log, not in the alert action, nowhere.

This is the part worth your attention: every test you'd normally run passes. The detection is, by every check that happens inside Splunk's search pipeline, working. It's only broken in the one place you don't habitually look – the rendered message a human actually reads.

A correct search is not a working detection.

The shape of it

The detection pulls from a JSON source – Okta, in this case, but it could be CloudTrail, Google Workspace, M365, GitHub audit, anything that ships nested JSON. The field you want in the alert lives inside a JSON array. Okta puts the affected user under target, an array, so spath extracts it as a field literally named target{}.displayName.

The alert action references it the obvious way:

actor=$result.actor.alternateId$ target=$result.target{}.displayName$

Enter fullscreen mode Exit fullscreen mode

The actor token resolves. The target token comes back empty. Same message, same syntax, one renders and one doesn't. The only difference is the curly braces.

Two faults, stacked

It's tempting to call this one bug. It's two, and you have to fix both.

Fault A: the token engine rejects the braces. Splunk's alert-action token substituter – the same engine behind dashboard input tokens – only accepts letters, digits, underscores, and dots inside a $...$ reference. Curly braces aren't on the list, and they're reserved for Splunk's own template constructs. When the engine scans result.target{}.displayName, it either stops at the { and goes looking for a field plainly named target (there isn't one – spath only ever created target{}.*), or it fails to match a valid token at all. Both roads end at an empty string.

Fault B: the field is multivalue. The {} in spath's output isn't decoration. It means "this came from a JSON array, it holds multiple values." A scalar token slot has no rule for rendering a multivalue field. Splunk doesn't pick the first value, or join them, or guess – it substitutes nothing.

Fault B is the one people miss. Rename the field to strip the braces and you've satisfied Fault A, but the token is still empty, because the field is still multivalue. Here's the full truth table:

What you reference Grammar OK? Scalar? Renders
$result.target{}.displayName$ No No empty
$result.target_array$ (rename only) Yes No empty
$result.target_first$ (rename + mvindex) Yes Yes the name

And the reason none of this ever pages you: token substitution has no "field not found" path that reaches the user. It's silent on purpose. Dashboard tokens are routinely optional, meant to disappear quietly when unset. The alert engine inherits that behavior and applies it to your detection, where a missing field is anything but optional.

The fix is one line doing three jobs

| eval target_displayName=mvindex('target{}.displayName', 0)

Enter fullscreen mode Exit fullscreen mode

Drop it in before the alert action sees the data. Each piece is load-bearing:

  • The single quotes tell SPL to read target{}.displayName as a literal field name. SPL itself has no problem with braces in field names – only the alert template engine does. Double quotes would make it a string literal, backticks would make it a macro. Single quotes are the only form that works here, and getting this wrong is the most common way people copy the pattern and stay broken.
  • mvindex(..., 0) collapses the multivalue field to its first element. That kills Fault B.
  • The rename to target_displayName gives the token a name built from characters the grammar accepts. That kills Fault A.

Now $result.target_displayName$ resolves, and the analyst sees a name.

Prove it to yourself in any environment

Don't trust a search that looks right. Trust what the alert renders. You can isolate Fault A in one query by putting a known-good token next to the broken one:

index=security sourcetype="OktaIM2:log" earliest=-1h
| head 1
| eval token_test="actor=$result.actor.alternateId$ target=$result.target{}.displayName$"
| table actor.alternateId target{}.displayName token_test

Enter fullscreen mode Exit fullscreen mode

The actor.alternateId half of token_test renders. The target{}.displayName half comes back empty, sitting right next to a table column that proves the underlying value exists. That gap – populated in the table, blank in the token – is the whole bug in one screen.

Then go wider. Grep your saved searches for {} inside any $...$ reference. Every match is a detection that's been shipping blanks. And bake the normalize-to-scalar step into your detection template, so multivalue JSON fields never reach an alert action with their braces intact.

The actual lesson

The search was never wrong. That's what makes this one mean. There's no syntax error to catch, no failed job to investigate, no red in CI. The field is in your results the entire time. The detection passes every test that runs inside the pipeline, and the only symptom is a name that isn't there – visible solely in Slack, and only if the analyst happens to notice the quotes are empty.

Test the render, not the search. The search tells you the detection works. Only the alert tells you it's useful.