v1.3.30 -- Scenario descriptions (deferred from v1.3.25)¶
A small, focused cosmetic-enrichment release. The Scenarios management UI shipped in v1.3.25 with names only; v1.3.30 adds the hub-catalogue description as a hover tooltip on each scenario name. Operators no longer need to context-switch to cscli scenarios inspect <name> or the upstream hub website to remember what a given scenario actually detects.
What it does¶
Hovering over a scenario name in the Scenarios tab now shows a one-line description from CrowdSec's hub catalogue. A small ⓘ glyph next to the name signals the tooltip is available. When a scenario has no description (rare; the hub catalogue currently covers 100% of the 779 entries), the name renders alone with no glyph -- no visual noise.
Why this took five releases to ship¶
v1.3.25's planning notes flagged "scenario descriptions" as deferred to a future release because the source file -- /etc/crowdsec/hub/.index.json -- has structural variance risk and access constraints. Investigating in v1.3.30:
- Structural risk: not real. The file is stable JSON, 775 KB, with
scenariosas a top-level map keyed by canonical name. Each entry hasdescription,path,version,versions, optionallyreferences. No surprises in CrowdSec v1.7.7. - Access constraint: real. The file is mode
0600owned byroot:rootinside thecrowdsec_configvolume. The panel runs asnobody(uid 65534) per the v1.0 hardening. The existing v1.3.25/crowdsec-stateread-only mount exposes the file but doesn't grant read access to the panel user. v1.3.25'sscenarios.Readerworks only because it reads symlink targets viaReadlink(path-string only, never opens file content); reading.index.jsoncontent is a fundamentally different access pattern.
What ships¶
Reverse-sentinel pattern (new)¶
Existing argos sentinels are panel -> crowdsec: the panel writes /data/shared/argos-*.{txt,yaml} and setup-appsec.sh consumes on next run. v1.3.30 introduces the inverse direction: crowdsec -> panel.
setup-appsec.sh::emit_scenarios_index (running as root inside the crowdsec container, can read 0600 hub files) parses .index.json with jq, slims it to a {canonical_name: description} map, and writes /shared/argos-scenarios-index.json. The shared volume's default umask gives the file mode 0644, so the panel-as- nobody can read it via /data/shared/argos-scenarios- index.json.
The slimmed file is ~115 KB (vs 775 KB for the original catalogue); the panel reads it on demand with mtime-based cache invalidation so a fresh setup-appsec.sh run is reflected on the very next API request.
Backend¶
backend/internal/security/scenarios/descriptions.go:DescriptionsLoaderwithGet(canonicalName) stringandLen() int. Nil-safe (Get on nil receiver returns ""). 7 unit tests covering missing file, valid lookup, mtime reload, malformed-file resilience, nil receiver, Reader integration, and the loader-nil-empty path.Scenariostruct gainsDescription string(json tagdescription,omitempty).Reader.Read()enriches each scenario withDescriptions.Get(canonicalName)when the loader is bound.scenarios.New()now binds the default descriptions loader (/data/shared/argos-scenarios-index.json) automatically. The handler layer needs no wiring change.
Crowdsec-side script¶
crowdsec/setup-appsec.sh::emit_scenarios_index: ~30 lines of bash.apk add --no-cache jq(idempotent; ~1.2s when already cached). jq slimmer:
.scenarios
| with_entries(select(.value.description != null
and .value.description != ""))
| with_entries(.value = .value.description)
Atomic-write via tempfile + rename. cmp-based no-op detection so runs that produce identical output don't rewrite the file.
Frontend¶
frontend/src/api/client.ts:SecurityScenarioItem.description?: stringfrontend/src/pages/Security.tsx: scenario name cell carries a nativetitle=attribute when description is present, plus a smallⓘglyph for discoverability. The glyph is absent when description is empty.
Smoke¶
scripts/smoke/scenario-descriptions.sh -- 5-step EFFECT verification:
- Run
setup-appsec.sh; assert/shared/argos-scenarios- index.jsonis valid JSON. GET /api/security/scenarios->>= 90%of installed scenarios carry a description (configurable viaCOVERAGE_PCTenv var).crowdsecurity/CVE-2017-9841description contains the substringCVE-2017-9841(configurable viaKNOWN_SCENARIO+KNOWN_SUBSTRING).- Rename the index file aside; assert the API still returns scenarios (graceful degrade -- no 500).
- Restore + touch the file; assert subsequent request picks it up (mtime invalidation).
Smoke gate (5/5 PASS on prod stack)¶
[1/5] /shared/argos-scenarios-index.json: 115433 bytes OK
[2/5] 54 / 54 scenarios have description (100%) OK
[3/5] CVE-2017-9841 description: "Detect CVE-2017-9841 exploits" OK
[4/5] graceful degrade: 54 scenarios returned without index OK
[5/5] restore + mtime touch -> description recovers OK
(54 here is the count of installed scenarios on this stack; the upstream hub catalogue has 779, all with descriptions.)
Files changed¶
crowdsec/setup-appsec.sh(emit_scenarios_index function + one call from apply_panel_sentinels)backend/internal/security/scenarios/scenarios.go(Scenario.Description, Reader.Descriptions field, default binding in New())backend/internal/security/scenarios/descriptions.go(new)backend/internal/security/scenarios/descriptions_test.go(new, 7 unit tests)frontend/src/api/client.ts(description field on type)frontend/src/pages/Security.tsx(tooltip + ⓘ glyph)backend/cmd/argos/main.go(argosVersion bump)frontend/package.json(version bump)scripts/smoke/scenario-descriptions.sh(new)docs/release-notes/v1.3.30.md(this file)CHANGELOG.md,mkdocs.yml
Upgrade¶
cd ~/argos-edge
git pull
make sync-prod # picks up the new setup-appsec.sh
# + scripts/smoke/* + Caddyfile
# change (none in v1.3.30)
docker compose -f /path/to/argos-prod/docker-compose.yml \
restart crowdsec # bind-mount inode refresh -- the
# v1.3.29 lesson; needed for the
# new emit_scenarios_index
# function to run inside the
# container
Then rebuild + redeploy the panel for the version-string bump + the new scenarios.DescriptionsLoader code:
cd ~/argos-prod
docker build -f backend/Dockerfile -t argos-prod-argos:v1.3.30 .
# update docker-compose.override.yml: image: argos-prod-argos:v1.3.30
docker compose up -d --force-recreate --no-deps argos
The first docker exec argos-prod-crowdsec /setup-appsec.sh after the deploy creates /shared/argos-scenarios-index.json (takes ~1.5s including the apk-add jq). Subsequent runs detect the file is unchanged and no-op.
Not changed¶
- All v1.3.29 backend / frontend / migration code unchanged.
- LAPI WAL mode (v1.3.28) untouched.
- Drift detector (v1.3.27) untouched.
- True detect mode (v1.3.29) untouched.
- Migration 031 still latest; no schema change in v1.3.30.
Reverse-sentinel pattern: documented for future use¶
The pattern v1.3.30 introduces is reusable any time the panel needs to surface CrowdSec internal state that lives in 0600 root-owned files inside the crowdsec_config volume. Future candidates:
- Per-scenario hub version (visible in
cscli scenarios listbut not directly in the panel). - Acquisition source listing (lives in
/etc/crowdsec/acquis*). - Bouncer registration state (
/etc/crowdsec/local_api_*).
Each becomes a small jq slimmer in setup-appsec.sh plus a small reader in backend/internal/security/scenarios/ (or a sibling package). No new mounts, no privilege escalation.