elastic / elastic/beats

[bug-hunter] Heartbeat api monitor expands dotted params keys instead of preserving literals

Open Beginner friendly
#52,328 1 comment 0 reactions 0 assignees View on GitHub
needs_team
Dominant language
Go
Stars
12.7k
Forks
5k
Avg merge
2d 15m
Merged PRs (30d)
385

Description

## Impact
`x-pack` Heartbeat `type: api` monitors that pass dotted `params` keys (for example `"subdomain.example.com"`) do not receive the original literal key. The key is dot-expanded into nested objects, which breaks journey code expecting `params["subdomain.example.com"]`.

This is user-impacting because `api` monitors reuse the browser source-job/params pipeline, but only `browser` streams get dotted-key preservation during config transform.

## Reproduction Steps
1. In `/home/runner/work/beats/beats/x-pack/heartbeat/cmd`, add this new test file:

```go
package cmd

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"

"github.com/elastic/elastic-agent-client/v7/pkg/client"
"github.com/elastic/elastic-agent-client/v7/pkg/proto"
)

func TestReproAPIParamsDottedKeys(t *testing.T) {
var rawIn proto.UnitExpectedConfig
err := readRawIn("testdata/simple-browser.in.json", &rawIn)
require.NoError(t, err, "failed to read fixture")

sourceMap := rawIn.GetSource().AsMap()
streams, ok := sourceMap["streams"].([]interface{})
require.True(t, ok, "expected streams slice")
require.NotEmpty(t, streams, "expected at least one stream")

stream, ok := streams[0].(map[string]interface{})
require.True(t, ok, "expected stream map")
stream["type"] = "api"
stream["params"] = map[string]interface{}{
"subdomain.example.com": "value2",
}

rawIn.Source, err = structpb.NewStruct(sourceMap)
require.NoError(t, err, "failed to rebuild proto source")

cfg, err := heartbeatCfg(&rawIn, &client.AgentInfo{ID: "abc7d0a8-ce04-4663-95da-ff6d537c268f", Version: "8.13.1"})
require.NoError(t, err, "heartbeatCfg returned error")

got, err := cfgToArrMap(cfg)
require.NoError(t, err, "failed to unpack cfg")
require.NotEmpty(t, got, "expected at least one monitor config")

params, ok := got[0]["params"].(map[string]interface{})
require.True(t, ok, "expected params map")
assert.Equal(t, "value2", params["subdomain.example.com"], "api monitor params dotted key should be preserved literally")
assert.NotContains(t, params, "subdomain", "api monitor params dotted key should not be expanded")
}
```

2. Run:

```bash
go test -count=1 -run TestReproAPIParamsDottedKeys ./x-pack/heartbeat/cmd
```

## Expected vs Actual
**Expected:** For `type: api`, dotted keys under `params` are preserved literally, so `params["subdomain.example.com"] == "value2"`.

**Actual:** Dotted key is expanded to nested structure; the literal key is missing.

Actual failing output:

```text
--- FAIL: TestReproAPIParamsDottedKeys (0.00s)
repro_api_params_test.go:43:
Error: Not equal:
expected: string("value2")
actual : ()
Messages: api monitor params dotted key should be preserved literally
repro_api_params_test.go:44:
Error: map[string]interface {}{"subdomain":map[string]interface {}{"example":map[string]interface {}{"com":"value2"}}} should not contain "subdomain"
Messages: api monitor params dotted key should not be expanded
FAIL
```

## Failing Test
(identical to the reproduction test above)

## Evidence
- `x-pack/heartbeat/cmd/root.go:79-80` only extracts params for `kind == "browser"`:
- `if kind, _ := stream["type"].(string); !ok || kind != "browser" { continue }`
- `x-pack/heartbeat/monitors/api/api.go:44` reuses browser source job:
- `sj, err := browser.NewSourceJob(cfg)`
- `x-pack/heartbeat/cmd/root_test.go:100-156` contains dotted-key preservation tests only for browser streams, not api streams.

---
[What is this?](https://ela.st/github-ai-tools) | [From workflow: Bug Hunter](https://github.com/elastic/beats/actions/runs/30539216718)

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
> - [x] expires on Aug 6, 2026, 11:51 AM UTC

Contributor guide

Open the contributing guide

Research direction

Start with x-pack/heartbeat/cmd/root.go:79-80 and the config path used by x-pack/heartbeat/monitors/api/api.go:44. Compare the existing dotted-key tests in x-pack/heartbeat/cmd/root_test.go:100-156 with the reproduction test described in the issue, then run the focused Go test. Done means the API monitor preserves params["subdomain.example.com"] literally without nested expansion.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, testing
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.