Skip to content

fix(security): redact setting secrets + make the PI board live server-side - #2

Merged
nicpozent merged 1 commit into
mainfrom
claude/code-examination-qx719b
Jul 12, 2026
Merged

fix(security): redact setting secrets + make the PI board live server-side#2
nicpozent merged 1 commit into
mainfrom
claude/code-examination-qx719b

Conversation

@nicpozent

Copy link
Copy Markdown
Owner

Follow-ups to #1, addressing gaps found in review.

🔴 Security fix — setting-secret leak

GET /settings returned the entire Setting table to any authenticated user with no permission gate. Since the Teams channel webhook URL is stored there, that secret was readable by any signed-in user — despite ADR-0060 stating it is "never returned to the client."

  • Redact secret-valued keys (suffixes webhookurl / secret / token / password) from the GET /settings response. New secret settings are redacted by default.
  • The Integrations UI only reads boolean toggles from /settings and gets the webhook's masked host from /integrations/teams/status, so nothing in the UI breaks. Reality now matches the ADR.

⚡ Real-time — server-driven change broadcast

The board previously only pushed live updates for changes made on the board itself. Now the change ping is server-driven:

  • IHubContext<BoardHub> is wired into the PI mutation endpoints — objectives, dependencies, iterations (create/update/delete) and board placement — broadcasting a contentless BoardChanged ping to the pi:{id} group after each commit.
  • A change made on the Objectives/Dependencies tab, or by another API client, now reaches every open board live — not just changes the board UI makes.
  • Best-effort: a transport hiccup can't fail the originating write (still no domain data over the hub; the DB remains the source of truth).
  • The client no longer relays changes over the hub (server is the single broadcaster); removed the now-dead NotifyChanged hub RPC and hook method.

Verification

  • Frontend build + eslint + 72 tests pass locally.
  • The exact CI Semgrep config reports 0 findings over server/.
  • .NET is compiled/tested by the API (build · test) CI job (no SDK in the authoring env).

Still open (documented, not in this PR)

  • Promote board placement to a first-class PiObjective.IterationId column — needs a generated EF migration.
  • Broadcast from the background sync services (Jira/ADO) too, so sync-driven changes push live.
  • Keyboard-accessible card moves (drag is mouse-only today).
  • Integration tests for the new endpoints/hub.

🤖 Generated with Claude Code

https://claude.ai/code/session_01F1g44VLAeYdf3FAoXsXRu7


Generated by Claude Code

…live board

Follow-ups to PR #1 (Teams notifications + real-time PI Program Board).

Security fix — setting-secret leak
- GET /settings returned the whole Setting table to any authenticated user,
  which exposed the Teams channel webhook URL (a secret) despite ADR-0060
  stating it is never returned. Redact secret-valued keys (suffixes
  webhookurl/secret/token/password) from the response; the connector's status
  endpoint still surfaces only a masked host. Reality now matches the ADR.

Real-time — server-driven change broadcast
- The board previously only pushed live updates for changes made on the board
  itself. Wire IHubContext<BoardHub> into the PI mutation endpoints (objectives,
  dependencies, iterations create/update/delete) and the board-placement
  endpoint, broadcasting a contentless "BoardChanged" ping to the increment
  group after each commit. Now a change made on another tab or by another API
  client reaches every open board live. Best-effort: a transport hiccup can't
  fail the originating write.
- Client no longer relays changes over the hub (server is the single
  broadcaster); removed the now-dead NotifyChanged hub RPC and hook method. The
  hook still sources presence + cursors.

Frontend build, lint and the 72-test suite pass; the exact CI Semgrep config
reports 0 findings over server/. .NET compiled by CI (no SDK locally).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1g44VLAeYdf3FAoXsXRu7
@nicpozent
nicpozent merged commit 5a99067 into main Jul 12, 2026
6 checks passed
nicpozent pushed a commit that referenced this pull request Jul 12, 2026
Addresses two of the remaining follow-ups from #1/#2.

Accessibility (ADR-0025 baseline)
- The board's only way to move a card between iterations was drag-and-drop
  (mouse-only). Add a native <select> on each card — fully keyboard-operable and
  screen-reader labelled — to move it to any iteration (or Unscheduled). Mouse
  users can still drag; both paths call the same placement mutation.

Integration tests (new server/Atlas.Tests/PiBoardAndSettingsTests.cs)
- GET /settings redacts secret-valued keys (teams.webhookUrl) while still
  returning non-secret toggles — regression guard for the #2 leak fix.
- PI board placement roundtrip: place → appears in the map; clear (null) →
  drops out; cross-increment objective → 400 (not 500).
- Teams status returns only the masked host, never the raw webhook (asserts the
  secret path/token never appear in the response).

Frontend build, lint and the 72-test suite pass. The .NET tests are compiled
and run by the API (build · test) CI job (no SDK in the authoring env).

Note: item #2 from the gap list (broadcast from Jira/ADO sync) is N/A — those
services write project tasks/sprints, not PI objectives/dependencies/iterations,
so they don't change board state. The IterationId migration still needs a
dotnet-ef environment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1g44VLAeYdf3FAoXsXRu7
nicpozent added a commit that referenced this pull request Jul 12, 2026
Adds a keyboard-operable per-card iteration select (a11y alternative to drag)
and integration tests for the endpoints added in #1/#2 (settings-secret
redaction, board-placement roundtrip + cross-increment rejection, Teams status
masking). All CI green.
nicpozent added a commit that referenced this pull request Jul 13, 2026
…e, screen splits (#25)

* feat(soa): automated platform-evidence linkage for Annex A controls

The SoA previously started every control blank. Now the ~20 ISO 27001
Annex A controls that the Atlas platform satisfies by construction carry
a standing evidence note mapped to the concrete mechanism — RBAC →
A.5.15/A.5.18/A.8.3, append-only audit log → A.8.15, backups → A.8.13,
OpenTelemetry → A.8.16, CI SAST/SCA/DAST → A.8.8/A.8.25/A.8.28/A.8.29,
secret redaction → A.8.11, TLS/CSP → A.8.20/A.8.24, Entra SSO →
A.5.16/A.5.17/A.8.5 — so a review starts from what the product provides.

- server/Soa.cs: PlatformEvidence map; GET attaches per-control
  autoEvidence + an autoEvidenced coverage count; PUT echoes it.
- web: a "Platform-evidenced" KPI + a per-row evidence chip.
- tests: assert the evidence surfaces (>=15), a known control (A.8.15 →
  audit log) carries it, and a control with no platform mechanism
  (A.7.1) does not.
- ADR-0066 updated (this was previously listed as a gap there).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1g44VLAeYdf3FAoXsXRu7

* feat(whiteboard): field-level merge for concurrent same-node edits

Evaluated a full CRDT/OT upgrade and declined it as disproportionate for
a bounded brainstorming canvas (heavy dep, would replace the typed rows
with an opaque CRDT doc + binary protocol, for a sub-RTT same-field race
that already self-heals). Implemented the proportionate step instead:
field-level merge.

- Co-editing node ops now send only the changed properties (WbNodePatch):
  a move sends geometry, a recolour sends the colour, a text edit sends
  the text. The server merges per field onto the existing row, so two
  people editing different aspects of one node (A moves, B recolours)
  both survive — no cross-field last-write-wins clobber. Create still
  sends a full node; only same-field edits remain LWW.
- server/Whiteboards.cs: WbNodePatch + merge-on-existing node upsert
  (backward compatible — a full-node body still creates/replaces).
- web: patchNode() for move/resize/recolour/text/keyboard-nudge.
- tests: color-only then move-only patches both persist and don't revert
  each other or the untouched size (444 API, 111 web green).
- ADR-0064 updated (convergence addendum + consequence).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1g44VLAeYdf3FAoXsXRu7

* refactor(screens): split Governance out of Project.tsx, data-governance out of Admin.tsx

Continues the ADR-0041 large-screen decomposition to the two biggest
remaining screens.

- Project.tsx (1617 → 1361): the Governance tab (stage-gate rail,
  review checkpoints, decision log + its modals) moves to
  project/Governance.tsx; Project.tsx imports it.
- Admin.tsx (1177 → 897): the data-governance sections (database-
  password rotation, GDPR data-subject rights + retention, Backups &
  restore) move to admin/DataGovernance.tsx; the shared section styles
  (GRADIENT, sectionTitle/Sub, colHeadStyle) move to admin/styles.ts so
  both files share one source.

Pure move — no behaviour change. Build + lint clean; 111 web tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1g44VLAeYdf3FAoXsXRu7

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

2 participants