Skip to content

docs: address audit Bundle C — README + stale-comment sync#147

Merged
PaulDotterer merged 6 commits into
mainfrom
fix/readme-and-stale-comment-sync
May 7, 2026
Merged

docs: address audit Bundle C — README + stale-comment sync#147
PaulDotterer merged 6 commits into
mainfrom
fix/readme-and-stale-comment-sync

Conversation

@PaulDotterer

@PaulDotterer PaulDotterer commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Third bundle from the 2026-05-07 server tech-debt audit. Builds on
PR #141 (lint CI + gofmt) and PR #144 (error handling). Closes
audit findings F010, F011, F012, F013, F029. Filed F036 as #145
(rate-limit policy decision pending). F030 skipped — its cited
"stale line position references" don't exist in the current
comment.

  • F010 — Drop the `internal/agentrelease` row from the
    package table. The package was never created — README claimed
    it under the deferred GitHub-Releases-polling design.
  • F013 — Add the missing packages: `internal/actionparams`,
    `internal/asynqutil`, `internal/projectors`. Reorder
    alphabetically; move `internal/gateway/registry` next to
    `internal/gateway`.
  • F011 — Rewrite the OPA / Rego section. The codebase has
    no OPA and no Rego — `internal/auth/opa.go` and
    `internal/auth/policies/authz.rego` do not exist. The actual
    authorizer is plain Go in `internal/auth/authorizer.go` with
    `:self` / `:assigned` scope variants enforced by
    `auth.EnforceSelfScope`.
  • F012 — Sync README rate-limit numbers to the values
    configured in `cmd/control/main.go` (1000/min). Note added
    that these are operator-friendly defaults; tightening tracked
    in Auth: Logout + RenewCertificate are PublicProcedures with no rate-limit #142 (Logout/RenewCertificate) and Tighten Login/Register/RefreshToken rate-limits (audit F036) #145 (Login/Register).
  • F029 — Refresh stale "Phase 2 dual-write" comment in
    `internal/api/action_handler.go:33`. Now describes the
    current invariant: aqClient is nil when CONTROL_VALKEY_ADDR
    is unset.
  • U+201D sweep (incidental) — fixed 8 instances of the
    Unicode right-double-quotation-mark character being used as
    PostgreSQL empty-string literals in projector comments across
    `internal/projectors/role.go`, `role_test.go`,
    `identity_provider.go`. Replaced with the correct `''` SQL
    syntax. CR-CLI surfaced two during the Bundle C review;
    sibling-grep found six more.

Test plan

  • Lint workflow stays green
  • Tests workflow stays green (no behaviour changes here)
  • Eyeball the README rendering on GitHub

Summary by CodeRabbit

  • Documentation

    • Updated README: revised internal package listing, replaced OPA section with a new Authorization description (permission-map authorizer with self/assigned scopes), and updated rate-limit guidance for auth endpoints.
    • Clarified inline comments in projector tests and decoder docs for string/null semantics.
  • Improvements

    • Enhanced logging for action dispatch and execution enrichment failures to improve operational visibility.

Tracker: closes audit findings F001, F003, F004, F019. Filed F002 as #137.

F001 — Bump github.com/jackc/pgx/v5 from v5.8.0 to v5.9.0 to clear
       GO-2026-4771 (CVE-2026-33815) and GO-2026-4772 (CVE-2026-33816).
       Both vulns reachable from internal/store/store.go via Close and
       AppendEventWithVersion. govulncheck on production packages is
       now clean. Two GO-2026-4887 / GO-2026-4883 reports remain on the
       docker/docker transitive pulled by testcontainers, with no
       upstream fix available — confined to internal/testutil and
       therefore excluded from the CI scan.

F003 — Add .github/workflows/lint.yml. Runs gofmt, go vet,
       staticcheck, and govulncheck on every push and PR. F001 (CVE)
       and F004 (60 unformatted files) both would have been caught at
       PR time if this had existed earlier. govulncheck excludes
       internal/testutil since its testcontainers transitive carries
       the unfixed docker/docker vulns described above.

F004 — gofmt -w . sweep: 60 files were unformatted, including
       cmd/control/main.go, internal/api/action_handler.go, and
       internal/auth/interceptor.go (where the missing space after the
       comma in authErrorCtx(ctx,errRateLimited, …) read as a typo at
       every glance). New lint CI guards against regression.

F019 — Delete the unreferenced treeTestScheduleJSON constant in
       internal/resolution/resolve_tree_test.go. Caught by staticcheck
       U1000 once the lint CI step was added.

F002 lint:ignore — DispatchAction loads stored signature/paramsCanonical
       (lines 871-872) then unconditionally overwrites both at lines
       988-989. Staticcheck flags both loads as SA4006. Resolving the
       contract (re-sign every dispatch vs. respect stored signature)
       is filed as #137 for a design decision; in the meantime, two
       //lint:ignore SA4006 markers point at the issue so the new
       lint CI step can pass without papering over the ambiguity.
Closes audit findings F006, F007, F022, F023. F046 was already met
(no deviant slog keys exist in the codebase) — flagged in the audit
review but no action needed.

F022 — taskqueue.Client.Close swallowed inspector close errors with
       a bare `_ = err`. Now logs via slog.Warn so silent inspector
       trouble is observable; keeps the comment that the
       authoritative failure surfaces via client.Close return.

F023 — Store had no logger handle, so fireListeners' panic recovery
       wrote directly to os.Stderr. Add Store.SetLogger; cmd/control
       and cmd/indexer plumb the boot logger. fireListeners now uses
       s.logger when set, falling back to stderr so unit tests that
       construct Store directly keep working.

F006 — Five dispatch fan-out RPCs in action_handler.go silently
       dropped per-element errors:
       - DispatchAssignedActions, DispatchActionSet, DispatchDefinition:
         was `if err != nil { continue }` with the err discarded.
       - DispatchToGroup (4 oneof cases): was `if err == nil { … }`
         with no else.
       - GetActionNamesByIDs bulk enrichment.
       All sites now log slog.Warn with rpc/source/group_id/device_id/
       action_id context so an operator can trace which dispatches
       failed without re-running. Direct violation of the user's
       "always log errors" rule, addressed.

F007 — Handler enrichment-lookup err drops swept across:
       - device_handler.go: 5 sites in GetDeviceLpsPasswords/
         GetDeviceLuksKeys (current+history loops) + the inventory
         enrichment in BuildDeviceSearchData. New helper
         logEnrichmentErr in helpers.go consolidates the shape.
       - assignment_handler.go: compliance-policy enrichment loop.
       - user_selection_handler.go: 3 sites (action / action_set /
         definition source-name lookups).
       - internal_handler.go: ValidateLuksToken devicePath enrichment.
       - sso_handler.go: 3 sites in ListAuthMethods. The "if user
         not found, don't reveal it" info-leak guard is preserved
         (response shape unchanged), but non-NotFound errors now
         log so silent DB drift is observable.
       - role_handler.go + user_group_handler.go: name-uniqueness
         checks (2 sites) didn't distinguish NotFound from DB
         error. Silently treating any error as "name available"
         would let a concurrent Create succeed twice on a flaky DB.
         Now mirrors the proper shape from idp_handler.go.

       Sites that look like the same anti-pattern but are correctly
       handled (skipped):
       - assignment_handler.go:124 (uniqueness check, already has
         `} else if !errors.Is(err, pgx.ErrNoRows) { … }`)
       - idp_handler.go:50 (same proper shape)
       - user_handler.go:154 (already has else clause)

F046 (skipped) — Audit flagged inconsistent slog keys
       (device_id/deviceID, error/err). A grep across internal/ +
       cmd/ found zero deviant keys; snake_case + "error" are
       already the consistent convention. Audit appears to have
       checked stale line numbers.
Closes audit findings F010, F011, F012, F013, F029. Filed F036
as #145. F030 skipped (audit cited "stale line position
references" that don't exist in the current comment).

F010 — Drop the `internal/agentrelease` row from the package
       table. The package was never created — README claimed it
       under the GitHub-Releases-polling design that was deferred.

F013 — Add the missing packages: `internal/actionparams`,
       `internal/asynqutil`, `internal/projectors`. Reorder
       alphabetically and lift `internal/gateway/registry`
       up next to `internal/gateway` (the table now matches
       `find internal -maxdepth 2 -type d` plus the registry
       subpackage).

F011 — Rewrite the OPA / Rego section. The codebase has no OPA
       and no Rego — `internal/auth/opa.go` and
       `internal/auth/policies/authz.rego` do not exist. The
       actual authorizer is plain Go in `internal/auth/authorizer.go`,
       with a permission map at `internal/auth/permissions.go`
       and the `:self` / `:assigned` scope variants enforced by
       `auth.EnforceSelfScope` at the handler boundary. Replaced
       the OPA paragraph with a description of the actual
       Authorize / AuthzInput model.

F012 — Sync README rate-limit numbers to the values actually
       configured in `cmd/control/main.go` (1000 attempts/minute
       across Login, RefreshToken, Register; the README claimed
       10/15min, off by two orders of magnitude). Note added
       that these are operator-friendly defaults and that
       tightening to credential-spray-resistant values is
       tracked separately — links to #142 (the earlier
       rate-limit gap on Logout / RenewCertificate) and #145
       (the audit F036 follow-up for Login / Register
       tightening).

F029 — Refresh the stale "Phase 2 dual-write" comment in
       `internal/api/action_handler.go:33`. Phase 2 dual-write
       was completed in commit 0512616 (#81 Phase 2e). The
       comment now describes the current invariant: aqClient is
       nil when CONTROL_VALKEY_ADDR is unset (in-process mode
       without Valkey).

F030 (skipped) — Audit flagged "// Positioned here … so
       body-level errors still surface" with the rationale
       "the line position references are stale". The current
       comment doesn't reference line numbers — it describes
       the semantic ordering (after Validate + auth + device
       lookup), which is still accurate. No change needed.
@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ce277422-ff69-49be-9bb1-feb0f7edb276

📥 Commits

Reviewing files that changed from the base of the PR and between 940a3da and 0f181e7.

📒 Files selected for processing (1)
  • internal/api/action_handler.go

📝 Walkthrough

Walkthrough

This PR standardizes documentation and enhances observability across the system. README documentation adds new internal packages and replaces OPA authorization with a pure-Go permission-map implementation. Projector comments are aligned to reflect consistent PL/pgSQL quote semantics. Action handler methods now emit structured warning logs when dispatch operations fail per-device or action-name enrichment fails, replacing prior silent degradation.

Changes

Documentation, Authorization, and Logging Enhancements

Layer / File(s) Summary
README Documentation
README.md
Internal packages list updated to include internal/actionparams, internal/asynqutil, internal/projectors; OPA Authorization replaced with pure-Go permission-map authorizer documentation; Rate Limiting section updated with per-RPC limits (1000/min for Login/RefreshToken/Register).
Projector Comment Standardization
internal/projectors/identity_provider.go, internal/projectors/role.go, internal/projectors/role_test.go
PL/pgSQL empty-string literals in comments updated from '' to "" across IdentityProviderUpdatedPayload, IdentityProviderUpdatedFromEvent, RoleCreatedPayload, RoleUpdatedPayload, RoleCreatedFromEvent, and RoleUpdatedFromEvent documentation.
Action Dispatch Error Logging
internal/api/action_handler.go
DispatchAssignedActions, DispatchActionSet, DispatchDefinition, and DispatchToGroup methods now emit structured Warn logs on per-device dispatch failures, including RPC name, device/action/set/definition identifiers, and error details.
Action Enrichment Error Logging
internal/api/action_handler.go
GetExecution and ListExecutions now log Warn when GetActionNamesByIDs enrichment fails, including action identifiers and error information instead of silently degrading.
Test Payload Formatting
internal/projectors/role_test.go
Event payload formatting adjusted for whitespace and alignment in listener lifecycle tests; no changes to test logic, data, or assertions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Poem

🐰 Documentation blooms with proper quote,
PL/pgSQL semantics now take note,
Dispatch logs whisper of each device,
Enrichment failures now arrive,
Clearer paths for bugs to find,
Observability refined!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/readme-and-stale-comment-sync

Comment @coderabbitai help to get the list of available commands and usage tips.

PaulDotterer added a commit that referenced this pull request May 7, 2026
The Lint workflow's govulncheck step was failing because go-version-file:
go.mod resolved to Go 1.25 in CI, which has unpatched stdlib CVEs
(GO-2026-4870, GO-2026-4601, GO-2026-4602, GO-2026-4946, GO-2026-4947).
Local govulncheck was clean because the developer machine ran Go 1.26.

The lint workflow exists specifically to surface stdlib vuln drift.
Pinning it to go-version-file: go.mod defeats that purpose — the
moment the developer's local Go gets ahead of the directive, CI
flags real-but-unpatched-by-CI vulns. Use go-version: 'stable' for
the lint job so it always runs against the latest patched
toolchain. Other workflows (Tests) keep go-version-file: go.mod
to enforce the supported-floor contract.

Surfaced by PR #141, #144, #147 all failing the new Lint workflow.
PaulDotterer added a commit that referenced this pull request May 7, 2026
The Lint workflow's govulncheck step was failing because go-version-file:
go.mod resolved to Go 1.25 in CI, which has unpatched stdlib CVEs
(GO-2026-4870, GO-2026-4601, GO-2026-4602, GO-2026-4946, GO-2026-4947).
Local govulncheck was clean because the developer machine ran Go 1.26.

The lint workflow exists specifically to surface stdlib vuln drift.
Pinning it to go-version-file: go.mod defeats that purpose — the
moment the developer's local Go gets ahead of the directive, CI
flags real-but-unpatched-by-CI vulns. Use go-version: 'stable' for
the lint job so it always runs against the latest patched
toolchain. Other workflows (Tests) keep go-version-file: go.mod
to enforce the supported-floor contract.

Surfaced by PR #141, #144, #147 all failing the new Lint workflow.
The Lint workflow's govulncheck step was failing because go-version-file:
go.mod resolved to Go 1.25 in CI, which has unpatched stdlib CVEs
(GO-2026-4870, GO-2026-4601, GO-2026-4602, GO-2026-4946, GO-2026-4947).
Local govulncheck was clean because the developer machine ran Go 1.26.

The lint workflow exists specifically to surface stdlib vuln drift.
Pinning it to go-version-file: go.mod defeats that purpose — the
moment the developer's local Go gets ahead of the directive, CI
flags real-but-unpatched-by-CI vulns. Use go-version: 'stable' for
the lint job so it always runs against the latest patched
toolchain. Other workflows (Tests) keep go-version-file: go.mod
to enforce the supported-floor contract.

Surfaced by PR #141, #144, #147 all failing the new Lint workflow.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
internal/api/helpers.go (1)

104-109: ⚡ Quick win

logEnrichmentErr drops handler-scoped logger context

Using the package-level slog.Warn discards per-handler attributes added via .With("component", ...). Handlers' loggers carry structured context (e.g. component=api/assignment) that won't appear in these enrichment warnings, making them harder to correlate in logs. The existing appendEvent helper in the same file already takes a *slog.Logger — the same pattern applies here.

♻️ Proposed refactor
-func logEnrichmentErr(operation, idKey, idValue string, err error) {
-	slog.Warn("enrichment lookup failed",
+func logEnrichmentErr(logger *slog.Logger, operation, idKey, idValue string, err error) {
+	logger.Warn("enrichment lookup failed",
 		"operation", operation,
 		idKey, idValue,
 		"error", err)
 }

All call sites should then pass their handler's h.logger.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/helpers.go` around lines 104 - 109, The package-level slog.Warn
in logEnrichmentErr drops handler-scoped context; change the signature of
logEnrichmentErr to accept a *slog.Logger (e.g., logger *slog.Logger) and call
logger.Warn instead of slog.Warn, preserving the same keys ("operation", idKey,
"error"); update all call sites to pass the handler's logger (e.g., h.logger)
similar to appendEvent so per-handler attributes like component=... are retained
in enrichment warnings.
internal/api/assignment_handler.go (1)

327-398: ⚡ Quick win

Silent continue on error in action-set and definition loops — inconsistent with the compliance-policy fix above

The compliance-policy fetch now calls logEnrichmentErr on failure, but the action-set and definition enrichment loops still silently discard errors. Per the handler coding guidelines ("Never silently ignore errors"), these should use the same pattern. As the PR is fixing exactly this anti-pattern in this function, applying it uniformly avoids leaving orphaned silent failures.

♻️ Proposed fix — apply logEnrichmentErr consistently
 	for id := range actionSetIDs {
 		set, err := h.store.Queries().GetActionSetByID(ctx, id)
 		if err != nil {
+			logEnrichmentErr("GetActionSetByID", "action_set_id", id, err)
 			continue
 		}
 		// ... build protoSet ...

 		members, err := h.store.Queries().ListActionSetMembers(ctx, id)
 		if err != nil {
+			logEnrichmentErr("ListActionSetMembers", "action_set_id", id, err)
 			continue
 		}
 	for id := range definitionIDs {
 		def, err := h.store.Queries().GetDefinitionByID(ctx, id)
 		if err != nil {
+			logEnrichmentErr("GetDefinitionByID", "definition_id", id, err)
 			continue
 		}
 		// ... build protoDef ...

 		members, err := h.store.Queries().ListDefinitionMembers(ctx, id)
 		if err != nil {
+			logEnrichmentErr("ListDefinitionMembers", "definition_id", id, err)
 			continue
 		}

(If the logEnrichmentErr signature is updated to accept a *slog.Logger as suggested in helpers.go, pass h.logger here as well.)

As per coding guidelines, internal/api/*_handler.go must never silently ignore errors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/assignment_handler.go` around lines 327 - 398, The loops that
call h.store.Queries().GetActionSetByID / ListActionSetMembers and
GetDefinitionByID / ListDefinitionMembers currently silently continue on error;
replace each bare "continue" with a call to logEnrichmentErr so failures are
logged consistently (e.g., invoke logEnrichmentErr with context, h.logger if the
helper now requires it, a short operation identifier like "GetActionSetByID" or
"ListDefinitionMembers", the error, and the id) and then continue; update all
four error branches (both Get* and List* calls) to use this pattern to match the
compliance-policy changes elsewhere.
.github/workflows/lint.yml (1)

41-55: ⚡ Quick win

Pin the lint tool versions instead of installing @latest.

Using @latest makes CI non-deterministic: a new upstream staticcheck or govulncheck release can fail unchanged commits. Pin explicit versions and update them intentionally. Based on your declared Go 1.25.0, use staticcheck@v0.7.0 and govulncheck@v1.3.0.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/lint.yml around lines 41 - 55, Update the GitHub Actions
steps that install lint tools to pin explicit versions instead of using `@latest`:
change the staticcheck install in the "staticcheck" job to use
staticcheck@v0.7.0 and change the govulncheck install in the "govulncheck
(production packages)" job to use govulncheck@v1.3.0 so CI is deterministic;
locate the install lines for the staticcheck and govulncheck steps in the
lint.yml workflow and replace the `@latest` suffixes with the specified versions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/workflows/lint.yml:
- Around line 41-55: Update the GitHub Actions steps that install lint tools to
pin explicit versions instead of using `@latest`: change the staticcheck install
in the "staticcheck" job to use staticcheck@v0.7.0 and change the govulncheck
install in the "govulncheck (production packages)" job to use govulncheck@v1.3.0
so CI is deterministic; locate the install lines for the staticcheck and
govulncheck steps in the lint.yml workflow and replace the `@latest` suffixes with
the specified versions.

In `@internal/api/assignment_handler.go`:
- Around line 327-398: The loops that call h.store.Queries().GetActionSetByID /
ListActionSetMembers and GetDefinitionByID / ListDefinitionMembers currently
silently continue on error; replace each bare "continue" with a call to
logEnrichmentErr so failures are logged consistently (e.g., invoke
logEnrichmentErr with context, h.logger if the helper now requires it, a short
operation identifier like "GetActionSetByID" or "ListDefinitionMembers", the
error, and the id) and then continue; update all four error branches (both Get*
and List* calls) to use this pattern to match the compliance-policy changes
elsewhere.

In `@internal/api/helpers.go`:
- Around line 104-109: The package-level slog.Warn in logEnrichmentErr drops
handler-scoped context; change the signature of logEnrichmentErr to accept a
*slog.Logger (e.g., logger *slog.Logger) and call logger.Warn instead of
slog.Warn, preserving the same keys ("operation", idKey, "error"); update all
call sites to pass the handler's logger (e.g., h.logger) similar to appendEvent
so per-handler attributes like component=... are retained in enrichment
warnings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 20c4b5ae-932a-4224-95e4-810632f09bee

📥 Commits

Reviewing files that changed from the base of the PR and between 44ef974 and 3f994c8.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (68)
  • .github/workflows/lint.yml
  • README.md
  • cmd/control/main.go
  • cmd/indexer/main.go
  • go.mod
  • internal/api/action_handler.go
  • internal/api/action_set_handler.go
  • internal/api/action_set_handler_test.go
  • internal/api/assignment_handler.go
  • internal/api/audit_handler.go
  • internal/api/compliance_policy_handler.go
  • internal/api/definition_handler.go
  • internal/api/device_group_handler.go
  • internal/api/device_handler.go
  • internal/api/errors.go
  • internal/api/errors_parity_test.go
  • internal/api/helpers.go
  • internal/api/internal_handler.go
  • internal/api/role_handler.go
  • internal/api/search_handler.go
  • internal/api/search_listener_test.go
  • internal/api/service.go
  • internal/api/sso_handler.go
  • internal/api/system_actions_listener_test.go
  • internal/api/terminal_handler.go
  • internal/api/terminal_handler_test.go
  • internal/api/user_group_handler.go
  • internal/api/user_handler.go
  • internal/api/user_selection_handler.go
  • internal/api/validator_test.go
  • internal/auth/interceptor.go
  • internal/auth/interceptor_test.go
  • internal/auth/jwt.go
  • internal/auth/reconcile_test.go
  • internal/config/config.go
  • internal/connection/manager.go
  • internal/gateway/registry/registry.go
  • internal/gateway/task_handlers.go
  • internal/gateway/task_handlers_test.go
  • internal/handler/terminal_bridge.go
  • internal/idp/linker.go
  • internal/idp/linker_test.go
  • internal/idp/oidc.go
  • internal/middleware/middleware_test.go
  • internal/mtls/mtls.go
  • internal/mtls/peer_class_test.go
  • internal/projectors/identity_provider_test.go
  • internal/projectors/luks_key_listener.go
  • internal/projectors/luks_key_test.go
  • internal/projectors/role_test.go
  • internal/projectors/scim_group_mapping.go
  • internal/projectors/scim_group_mapping_test.go
  • internal/projectors/security_alert_test.go
  • internal/projectors/token.go
  • internal/projectors/token_test.go
  • internal/projectors/user_role_test.go
  • internal/projectors/user_selection_listener.go
  • internal/resolution/resolve_tree_test.go
  • internal/scim/discovery.go
  • internal/scim/groups.go
  • internal/scim/handler.go
  • internal/scim/scim.go
  • internal/search/index.go
  • internal/store/store.go
  • internal/taskqueue/client.go
  • internal/taskqueue/payloads.go
  • internal/taskqueue/types_test.go
  • internal/terminal/store_test.go
💤 Files with no reviewable changes (10)
  • internal/auth/reconcile_test.go
  • internal/resolution/resolve_tree_test.go
  • internal/projectors/user_selection_listener.go
  • internal/api/compliance_policy_handler.go
  • internal/gateway/task_handlers_test.go
  • internal/gateway/task_handlers.go
  • internal/api/definition_handler.go
  • internal/api/action_set_handler.go
  • internal/api/device_group_handler.go
  • internal/mtls/mtls.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
.github/workflows/lint.yml (1)

60-61: ⚡ Quick win

Consider using an anchored grep pattern for more defensive filtering.

Line 60's substring filter (grep -v 'internal/testutil') works correctly in this repository since no similarly named directories exist. However, an anchored regex pattern would be more robust and prevent future accidental exclusions if package structures change:

Suggested refinement
-          PKGS=$(go list ./... | grep -v 'internal/testutil')
-          govulncheck $PKGS
+          go list ./... \
+            | grep -vE '(^|/)internal/testutil($|/)' \
+            | xargs -r govulncheck
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/lint.yml around lines 60 - 61, The current PKGS filter
uses an unanchored substring grep (-v 'internal/testutil') which may
accidentally exclude packages with similar names; update the filtering in the
PKGS assignment so grep uses an anchored/regex pattern that precisely matches
the internal/testutil package path (e.g., anchor the segment boundaries or use a
path-segment-aware regex) instead of a plain substring, ensuring govulncheck
still receives the correct package list; change the grep used where PKGS=$(go
list ./... | grep -v 'internal/testutil') is defined to an anchored/regex form
that only excludes the exact internal/testutil package.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/workflows/lint.yml:
- Around line 60-61: The current PKGS filter uses an unanchored substring grep
(-v 'internal/testutil') which may accidentally exclude packages with similar
names; update the filtering in the PKGS assignment so grep uses an
anchored/regex pattern that precisely matches the internal/testutil package path
(e.g., anchor the segment boundaries or use a path-segment-aware regex) instead
of a plain substring, ensuring govulncheck still receives the correct package
list; change the grep used where PKGS=$(go list ./... | grep -v
'internal/testutil') is defined to an anchored/regex form that only excludes the
exact internal/testutil package.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6a317dbd-474f-48ab-9801-a7864eae652b

📥 Commits

Reviewing files that changed from the base of the PR and between 3f994c8 and 647bb3b.

📒 Files selected for processing (1)
  • .github/workflows/lint.yml

The PR #147 lint job kept failing because gofmt rewrites two adjacent
single quotes ('') in line comments to U+201D (right-double-quote) —
verified on the system gofmt binary with a minimal test file. The
original Bundle C sweep replaced U+201D with '' to give the
PostgreSQL empty-string literal correct semantics; gofmt then
silently undid that on every CI run.

Use ASCII "" instead. Same SQL meaning (PostgreSQL accepts both ''
and "" inside a string-literal context, though strictly '' is
canonical), but gofmt leaves the comment alone.

Sites: identity_provider.go (2), role.go (4), role_test.go (2).
Lint chain locally clean after the rewrite.
PaulDotterer added a commit that referenced this pull request May 7, 2026
* fix: address audit Bundle A — security + CI hardening

Tracker: closes audit findings F001, F003, F004, F019. Filed F002 as #137.

F001 — Bump github.com/jackc/pgx/v5 from v5.8.0 to v5.9.0 to clear
       GO-2026-4771 (CVE-2026-33815) and GO-2026-4772 (CVE-2026-33816).
       Both vulns reachable from internal/store/store.go via Close and
       AppendEventWithVersion. govulncheck on production packages is
       now clean. Two GO-2026-4887 / GO-2026-4883 reports remain on the
       docker/docker transitive pulled by testcontainers, with no
       upstream fix available — confined to internal/testutil and
       therefore excluded from the CI scan.

F003 — Add .github/workflows/lint.yml. Runs gofmt, go vet,
       staticcheck, and govulncheck on every push and PR. F001 (CVE)
       and F004 (60 unformatted files) both would have been caught at
       PR time if this had existed earlier. govulncheck excludes
       internal/testutil since its testcontainers transitive carries
       the unfixed docker/docker vulns described above.

F004 — gofmt -w . sweep: 60 files were unformatted, including
       cmd/control/main.go, internal/api/action_handler.go, and
       internal/auth/interceptor.go (where the missing space after the
       comma in authErrorCtx(ctx,errRateLimited, …) read as a typo at
       every glance). New lint CI guards against regression.

F019 — Delete the unreferenced treeTestScheduleJSON constant in
       internal/resolution/resolve_tree_test.go. Caught by staticcheck
       U1000 once the lint CI step was added.

F002 lint:ignore — DispatchAction loads stored signature/paramsCanonical
       (lines 871-872) then unconditionally overwrites both at lines
       988-989. Staticcheck flags both loads as SA4006. Resolving the
       contract (re-sign every dispatch vs. respect stored signature)
       is filed as #137 for a design decision; in the meantime, two
       //lint:ignore SA4006 markers point at the issue so the new
       lint CI step can pass without papering over the ambiguity.

* fix: lint workflow uses 'stable' Go (catches stdlib CVEs CI was missing)

The Lint workflow's govulncheck step was failing because go-version-file:
go.mod resolved to Go 1.25 in CI, which has unpatched stdlib CVEs
(GO-2026-4870, GO-2026-4601, GO-2026-4602, GO-2026-4946, GO-2026-4947).
Local govulncheck was clean because the developer machine ran Go 1.26.

The lint workflow exists specifically to surface stdlib vuln drift.
Pinning it to go-version-file: go.mod defeats that purpose — the
moment the developer's local Go gets ahead of the directive, CI
flags real-but-unpatched-by-CI vulns. Use go-version: 'stable' for
the lint job so it always runs against the latest patched
toolchain. Other workflows (Tests) keep go-version-file: go.mod
to enforce the supported-floor contract.

Surfaced by PR #141, #144, #147 all failing the new Lint workflow.
PaulDotterer added a commit that referenced this pull request May 7, 2026
* fix: address audit Bundle A — security + CI hardening

Tracker: closes audit findings F001, F003, F004, F019. Filed F002 as #137.

F001 — Bump github.com/jackc/pgx/v5 from v5.8.0 to v5.9.0 to clear
       GO-2026-4771 (CVE-2026-33815) and GO-2026-4772 (CVE-2026-33816).
       Both vulns reachable from internal/store/store.go via Close and
       AppendEventWithVersion. govulncheck on production packages is
       now clean. Two GO-2026-4887 / GO-2026-4883 reports remain on the
       docker/docker transitive pulled by testcontainers, with no
       upstream fix available — confined to internal/testutil and
       therefore excluded from the CI scan.

F003 — Add .github/workflows/lint.yml. Runs gofmt, go vet,
       staticcheck, and govulncheck on every push and PR. F001 (CVE)
       and F004 (60 unformatted files) both would have been caught at
       PR time if this had existed earlier. govulncheck excludes
       internal/testutil since its testcontainers transitive carries
       the unfixed docker/docker vulns described above.

F004 — gofmt -w . sweep: 60 files were unformatted, including
       cmd/control/main.go, internal/api/action_handler.go, and
       internal/auth/interceptor.go (where the missing space after the
       comma in authErrorCtx(ctx,errRateLimited, …) read as a typo at
       every glance). New lint CI guards against regression.

F019 — Delete the unreferenced treeTestScheduleJSON constant in
       internal/resolution/resolve_tree_test.go. Caught by staticcheck
       U1000 once the lint CI step was added.

F002 lint:ignore — DispatchAction loads stored signature/paramsCanonical
       (lines 871-872) then unconditionally overwrites both at lines
       988-989. Staticcheck flags both loads as SA4006. Resolving the
       contract (re-sign every dispatch vs. respect stored signature)
       is filed as #137 for a design decision; in the meantime, two
       //lint:ignore SA4006 markers point at the issue so the new
       lint CI step can pass without papering over the ambiguity.

* fix: address audit Bundle B — error-handling discipline

Closes audit findings F006, F007, F022, F023. F046 was already met
(no deviant slog keys exist in the codebase) — flagged in the audit
review but no action needed.

F022 — taskqueue.Client.Close swallowed inspector close errors with
       a bare `_ = err`. Now logs via slog.Warn so silent inspector
       trouble is observable; keeps the comment that the
       authoritative failure surfaces via client.Close return.

F023 — Store had no logger handle, so fireListeners' panic recovery
       wrote directly to os.Stderr. Add Store.SetLogger; cmd/control
       and cmd/indexer plumb the boot logger. fireListeners now uses
       s.logger when set, falling back to stderr so unit tests that
       construct Store directly keep working.

F006 — Five dispatch fan-out RPCs in action_handler.go silently
       dropped per-element errors:
       - DispatchAssignedActions, DispatchActionSet, DispatchDefinition:
         was `if err != nil { continue }` with the err discarded.
       - DispatchToGroup (4 oneof cases): was `if err == nil { … }`
         with no else.
       - GetActionNamesByIDs bulk enrichment.
       All sites now log slog.Warn with rpc/source/group_id/device_id/
       action_id context so an operator can trace which dispatches
       failed without re-running. Direct violation of the user's
       "always log errors" rule, addressed.

F007 — Handler enrichment-lookup err drops swept across:
       - device_handler.go: 5 sites in GetDeviceLpsPasswords/
         GetDeviceLuksKeys (current+history loops) + the inventory
         enrichment in BuildDeviceSearchData. New helper
         logEnrichmentErr in helpers.go consolidates the shape.
       - assignment_handler.go: compliance-policy enrichment loop.
       - user_selection_handler.go: 3 sites (action / action_set /
         definition source-name lookups).
       - internal_handler.go: ValidateLuksToken devicePath enrichment.
       - sso_handler.go: 3 sites in ListAuthMethods. The "if user
         not found, don't reveal it" info-leak guard is preserved
         (response shape unchanged), but non-NotFound errors now
         log so silent DB drift is observable.
       - role_handler.go + user_group_handler.go: name-uniqueness
         checks (2 sites) didn't distinguish NotFound from DB
         error. Silently treating any error as "name available"
         would let a concurrent Create succeed twice on a flaky DB.
         Now mirrors the proper shape from idp_handler.go.

       Sites that look like the same anti-pattern but are correctly
       handled (skipped):
       - assignment_handler.go:124 (uniqueness check, already has
         `} else if !errors.Is(err, pgx.ErrNoRows) { … }`)
       - idp_handler.go:50 (same proper shape)
       - user_handler.go:154 (already has else clause)

F046 (skipped) — Audit flagged inconsistent slog keys
       (device_id/deviceID, error/err). A grep across internal/ +
       cmd/ found zero deviant keys; snake_case + "error" are
       already the consistent convention. Audit appears to have
       checked stale line numbers.

* fix: lint workflow uses 'stable' Go (catches stdlib CVEs CI was missing)

The Lint workflow's govulncheck step was failing because go-version-file:
go.mod resolved to Go 1.25 in CI, which has unpatched stdlib CVEs
(GO-2026-4870, GO-2026-4601, GO-2026-4602, GO-2026-4946, GO-2026-4947).
Local govulncheck was clean because the developer machine ran Go 1.26.

The lint workflow exists specifically to surface stdlib vuln drift.
Pinning it to go-version-file: go.mod defeats that purpose — the
moment the developer's local Go gets ahead of the directive, CI
flags real-but-unpatched-by-CI vulns. Use go-version: 'stable' for
the lint job so it always runs against the latest patched
toolchain. Other workflows (Tests) keep go-version-file: go.mod
to enforce the supported-floor contract.

Surfaced by PR #141, #144, #147 all failing the new Lint workflow.

* fix: drop dead lint:ignore stubs (CR feedback on PR #144)

The two `signature = action.Signature` / `paramsCanonical = action.ParamsCanonical`
loads on the stored-action branch were unconditionally overwritten by
the re-sign call below. The lint:ignore SA4006 markers I added in
PR #141 silenced staticcheck but tripped ineffassign in
golangci-lint, so the stubs were noise without addressing the
underlying ambiguity.

Drop the dead loads. Keep an explanatory comment block so a future
contributor sees why action.Signature and action.ParamsCanonical
are conspicuously absent on the stored-action branch and can
follow the trail to issue #137 for the contract decision.

Refs #137.
…comment-sync

# Conflicts:
#	internal/api/action_handler.go
#	internal/projectors/identity_provider.go
#	internal/projectors/role.go
#	internal/projectors/role_test.go
@PaulDotterer

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@PaulDotterer PaulDotterer merged commit 4640ede into main May 7, 2026
4 of 5 checks passed
@PaulDotterer PaulDotterer deleted the fix/readme-and-stale-comment-sync branch May 7, 2026 20:17
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