Skip to content

feat(dynatrace): validate tokens via v2 lookup + scope-based blast radius#5

Open
beer4code wants to merge 1 commit into
puck-security:mainfrom
beer4code:feat/dynatrace-detector
Open

feat(dynatrace): validate tokens via v2 lookup + scope-based blast radius#5
beer4code wants to merge 1 commit into
puck-security:mainfrom
beer4code:feat/dynatrace-detector

Conversation

@beer4code

Copy link
Copy Markdown

Problem

The existing Dynatrace detector was effectively non-operational. It templated Base on {endpoint} with no tenant resolution (so it never ran without a manual --endpoint). Its whoami hit /api/v1/time - a non-existent path that 404s, so it could never actually confirm or deny a token, and it did zero blast-radius modeling.

Suggested Changes

  • Recognizer (dynatrace.go): matches the full token shape dt0[a-z][0-9]{2}.<id>.<secret> (covers dt0s16 platform tokens that gitleaks misses), auto-discovers the tenant URL from the same blob, normalizes the 3rd-gen apps host to the classic API host, and falls back to env vars / --endpoint (or a needs-endpoint hint).
    • gitleaks dynatrace-api-token pattern is outdated but as the project is in maintenance mode, it seemed reasonable to redefine a (practically validated) pattern with support for all currently issued token types directly in geiger.
  • Recon (catalog_header.go): validates via POST /api/v2/apiTokens/lookup (read-only):
    • 401 = dead token,
    • 403 = valid token that can't introspect itself,
    • 200 = parse owner/expiry/last-used-IP/scopes

Severity was aligned to geiger's tiers:

  • A curated force-multiplier set (token minting, tenant-config write, extension deploy/RCE, wiretap, credential injection, disable PII masking) → HIGH, and CRITICAL when a token stacks ≥ 2.
  • Sensitive reads (logs, session replay, export) & meaningful integrity writes (ingest, entity/problem/fleet mutation) → MEDIUM.
  • Operational reads, trivial writes, and credentialVault.read (metadata-only and no secret material is extractable) → LOW.

Validation

  • 11 unit tests (recognizer, host normalization, recon per-tier).
  • Verified live against real tokens: a dt0c01 ingest token → MEDIUM, a dt0s16 platform token → 403 handled as valid-but-uninspectable (LOW), an invalid token → 401.

Real-World Example

$ echo "dt0c01.<id>.<secret> https://<tenant>.live.dynatrace.com" | geiger --live

geiger dev · stdin · live
[MEDIUM] dynatrace …<redacted> (from stdin: DYNATRACE_API_TOKEN)
  name                  : ai-obs-2026-04-19T18:11:14.950Z
  owner                 : <redacted>@example.com
  personal access token : false
  last used             : 2026-07-11T12:44:12.948Z
  last used IP          : 95.212.17.***
  scopes                : events.ingest, metrics.ingest, openTelemetryTrace.ingest, logs.ingest   ⚠
  integrity             : integrity/availability write — inject/forge telemetry (poison dashboards, log injection, mask attacks), corrupt topology, hide problems, or tamper the OneAgent fleet   ⚠
  validated live        : 2026-07-11T16:02:34Z
  → Dynatrace token — telemetry/config mutation (integrity & evasion)
    curl -sS -X POST -H "Authorization: Api-Token $DYNATRACE_API_TOKEN" -H 'Accept: …json' -H 'Content-Type: …json' -H 'User-Agent: geiger/dev' --data "{\"token\":\"$DYNATRACE_API_TOKEN\"}" 'https://<tenant>.live.dynatrace.com/api/v2/apiTokens/lookup'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant