fix(security): close P-7 — validate campaignId at api-client boundaries#54
Merged
Conversation
… tests (FM-SEC-CHUNK-5 part 1)
New scripts/_settings-validation.mjs exporting isValidCampaignId(id)
and describeCampaignIdError(id). 16 behavioral tests in
tools/test-campaign-id-validation.mjs.
Pair commit follows with the api-client.mjs wiring (import + validate
at fetch/uploadMedia boundaries).
Cites: 2026-05-21-core-tenets §T-B1
reports/foundry/2026-05-22-fm-security-audit.md §2 P-7, §4 Chunk 5
api-client.mjs::fetch and uploadMedia now validate getSetting('campaignId')
via _validateCampaignIdOrThrow before interpolating into the URL. Pair
commit shipped the validator helper + tests.
On invalid: throws Error + surfaces ui.notifications.error with an
operator-actionable message ("Open Module Settings → Chronicle Sync →
Campaign ID and paste the campaign UUID from Chronicle"). Closes the
SSRF / URL-escape surface AND replaces the today-confusing 404 from
Chronicle with a clear "your config is wrong, here's how to fix" notice.
Test suite: 250 baseline + 16 new = 266/266 pass.
Cites: 2026-05-21-core-tenets §T-B1
reports/foundry/2026-05-22-fm-security-audit.md §2 P-7, §4 Chunk 5
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cites: 2026-05-21-core-tenets §T-B1; reports/foundry/2026-05-22-fm-security-audit.md §2 P-7, §4 Chunk 5; dispatches/foundry/FM-SEC-CHUNK-5.md
Security implication: closes P-7 (campaignId interpolation surface). A misconfigured campaignId would silently 404 from Chronicle or — worst case — escape the campaign scope via path injection. Now: validate at the api-client boundary; reject + clear notification if invalid.
Consumer-verified: n/a — operator-controlled setting; this PR adds the validation invariant.
Foundry compatibility: v12 / v13 / v14 — pure JS regex, optional-chaining for
ui.notificationsaccessor.Mockup: n/a — error notification surface only.
What this changes
scripts/_settings-validation.mjs(NEW)isValidCampaignId(id)(UUID 8-4-4-4-12 hex regex, case-insensitive) +describeCampaignIdError(id)(returns null for valid; otherwise an operator-actionable error message).scripts/api-client.mjsdescribeCampaignIdError. Adds module-private_validateCampaignIdOrThrow(campaignId)helper. Calls it fromfetch(line ~201) anduploadMedia(line ~375).tools/test-campaign-id-validation.mjs(NEW)../, non-string types), error-message coverage, static-source integration checks pinning the validator calls in bothfetchanduploadMedia.Why
The Foundry-side security audit (
§2 P-7,§4 Chunk 5) flagged the campaignId interpolation surface. Today's behavior: a misconfigured campaignId (typo / empty /../) flows into${baseUrl}/api/v1/campaigns/${campaignId}${path}and either silently 404s from Chronicle (best case) or escapes the campaign scope. After: each Chronicle API call validates campaignId at the boundary; invalid → throw +ui.notifications.errorwith an actionable "Open Module Settings → Chronicle Sync → Campaign ID" message.Test plan
node --test tools/test-*.mjspasses locally (250 baseline + 16 new = 266/266)node tools/check-package-descriptor.mjspassesnot-a-uuid); verify error notification appears + console.error has the clear message; configure with valid UUID; verify normal operation resumesTenet self-check
chronicle-foundry-moduleStop-and-flag
None encountered. Two notes:
_doConnectconstructs${wsBase}/ws?token=...&client=foundry-modulewithout campaignId interpolation; campaign association rides on the token, not the URL path). No security gap.Note on commit history
Two commits via MCP push_files (signing infra failure on chronicle-foundry-module — same as PR #49/50/51/52/53).
🤖 Cites
dispatches/foundry/FM-SEC-CHUNK-5.md. Generated by Claude Code.Generated by Claude Code