Skip to content

Add JSON output format (--output json) for list, status, and show - #888

Open
Muhammaduazir69 wants to merge 1 commit into
DataDog:mainfrom
Muhammaduazir69:feat/cli-json-output
Open

Add JSON output format (--output json) for list, status, and show#888
Muhammaduazir69 wants to merge 1 commit into
DataDog:mainfrom
Muhammaduazir69:feat/cli-json-output

Conversation

@Muhammaduazir69

Copy link
Copy Markdown

What this does

Adds a global --output/-o flag (table | json, default table) so the read-only commands can emit machine-readable JSON:

  • stratus list -o json — the full technique catalog (id, name, platform, isSlow, isIdempotent, mitreAttackTactics)
  • stratus status -o json — per-technique state (id, name, state)
  • stratus show <id> -o json — full technique detail (description, detection, MITRE tactics, framework mappings)

Closes #241.

Why

The only way to consume Stratus' technique inventory or state today is by scraping the rendered tables. JSON output makes Stratus scriptable for automation, CI pipelines, and SIEM/detection-engineering ingestion (the use case raised in #241).

Example

$ stratus list -o json | jq '.[0]'
{
  "id": "aws.credential-access.ec2-get-password-data",
  "name": "Retrieve EC2 Password Data",
  "platform": "AWS",
  "isSlow": false,
  "isIdempotent": true,
  "mitreAttackTactics": ["Credential Access"]
}

Implementation notes

  • New v2/cmd/stratus/cmd/output.go defines stable JSON view models decoupled from stratus.AttackTechnique — that struct carries Detonate/Revert closures and embedded Terraform []byte, which must not (and cannot) be serialized. This keeps the JSON contract stable even if the internal struct changes.
  • The flag is a root persistent flag, validated in PersistentPreRunE, so every command inherits it and unknown formats are rejected early.
  • Default behaviour is unchanged: without -o json, the existing colored tables render exactly as before.
  • main() now exits non-zero when a command (or flag validation) fails. Previously the error from RootCmd.Execute() was discarded, so even invalid input exited 0 — which would make the new -o validation (and any other error) undetectable in scripts.

Tests

  • Adds the first unit tests under cmd/ (output_test.go): JSON mapping for list/show, framework-mapping serialization, empty-slice vs null stability, no HTML-escaping, and --output validation.
  • go test ./..., go vet, gofmt, and staticcheck ./cmd/... all pass locally.

Scope

Scoped to the read-only commands (list, status, show) that have structured output. detonate/warmup/revert/cleanup stream progress logs and are intentionally left as-is; structured result output for those can be a follow-up.

Introduces a global --output/-o flag (table|json, default table) so the
read-only commands can emit machine-readable JSON for automation, CI, and
SIEM ingestion.

- New cmd/stratus/cmd/output.go defines stable JSON view models, decoupled
  from stratus.AttackTechnique (whose Detonate/Revert closures and embedded
  Terraform code are not serializable).
- Global persistent flag validated via PersistentPreRunE.
- main() now exits non-zero when a command fails, so the CLI is usable in
  scripts (previously the Execute() error was ignored).
- First unit tests under cmd/ covering the JSON mapping and flag validation.
- Documents the new flag on the list/status/show command pages.

Closes DataDog#241

Signed-off-by: Muhammad Uzair <muhammaduzairr69@gmail.com>
@Muhammaduazir69
Muhammaduazir69 requested review from a team as code owners June 30, 2026 18:22
@christophetd christophetd self-assigned this Jul 7, 2026
@christophetd

christophetd commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Hi @Muhammaduazir69, thanks for the contribution!

Since we're at it and we noticed you made the -o flag global, how do you think it should behave for commands that output logs (e.g. stratus detonate)? Would you consume a JSON output from these as well, knowing they are logs, and not a structured output like we'd have with stratus status xxx -o json?

@christophetd christophetd added the kind/enhancement New feature or request label Jul 7, 2026
@Muhammaduazir69

Copy link
Copy Markdown
Author

Thanks @christophetd! Agreed — logs and structured output are different things, and turning the log stream itself into JSON (one object per log line) would just be noise; someone consuming detonate doesn't want the logs, they want the outcome.

Here's how I'd make the global -o behave consistently for the log-emitting commands (detonate, warmup, revert, cleanup):

  • send the human-readable progress logs to stderr (in JSON mode), and
  • print a single structured result to stdout at the end — one object per technique with its outcome. For detonate/warmup that'd look like:
[
  { "id": "aws.defense-evasion.cloudtrail-stop", "state": "DETONATED", "success": true, "error": null },
  { "id": "aws.persistence.iam-backdoor-user", "state": "WARM", "success": false, "error": "..." }
]

That gives automation what it actually needs (per-technique result + resulting state, alongside the existing non-zero exit code) while keeping stdout valid JSON — the same split as terraform -json/kubectl: diagnostics on stderr, machine-readable result on stdout.

Two notes from the current code:

  • revert and cleanup already end by calling the status view, so in -o json they'd naturally emit the status summary — the main change there is just routing progress logs to stderr.
  • logs currently go to stdout (setupLogging uses log.SetOutput(os.Stdout)), so "logs → stderr in JSON mode" is the shared prerequisite.

On scope — happy either way:

  1. keep this PR focused on the read-only commands (list/status/show), where JSON is unambiguous, and add the structured result-summary for the action commands as a follow-up PR; or
  2. extend this PR now to cover the action commands with the stderr-logs + stdout-summary behavior above.

I lean toward (1) to keep it reviewable, but glad to do (2) here if you'd prefer. And if you'd rather not expose -o on the action commands at all for now, I can scope the flag so it isn't silently accepted there. Let me know which you prefer and I'll implement it.

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

Labels

kind/enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add JSON output

2 participants