docs: address audit Bundle C — README + stale-comment sync#147
Conversation
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.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis 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. ChangesDocumentation, Authorization, and Logging Enhancements
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
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.
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.
There was a problem hiding this comment.
🧹 Nitpick comments (3)
internal/api/helpers.go (1)
104-109: ⚡ Quick win
logEnrichmentErrdrops handler-scoped logger contextUsing the package-level
slog.Warndiscards 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 existingappendEventhelper 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 winSilent
continueon error in action-set and definition loops — inconsistent with the compliance-policy fix aboveThe compliance-policy fetch now calls
logEnrichmentErron 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
logEnrichmentErrsignature is updated to accept a*slog.Loggeras suggested inhelpers.go, passh.loggerhere as well.)As per coding guidelines,
internal/api/*_handler.gomust 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 winPin the lint tool versions instead of installing
@latest.Using
@latestmakes CI non-deterministic: a new upstreamstaticcheckorgovulncheckrelease can fail unchanged commits. Pin explicit versions and update them intentionally. Based on your declared Go 1.25.0, usestaticcheck@v0.7.0andgovulncheck@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
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (68)
.github/workflows/lint.ymlREADME.mdcmd/control/main.gocmd/indexer/main.gogo.modinternal/api/action_handler.gointernal/api/action_set_handler.gointernal/api/action_set_handler_test.gointernal/api/assignment_handler.gointernal/api/audit_handler.gointernal/api/compliance_policy_handler.gointernal/api/definition_handler.gointernal/api/device_group_handler.gointernal/api/device_handler.gointernal/api/errors.gointernal/api/errors_parity_test.gointernal/api/helpers.gointernal/api/internal_handler.gointernal/api/role_handler.gointernal/api/search_handler.gointernal/api/search_listener_test.gointernal/api/service.gointernal/api/sso_handler.gointernal/api/system_actions_listener_test.gointernal/api/terminal_handler.gointernal/api/terminal_handler_test.gointernal/api/user_group_handler.gointernal/api/user_handler.gointernal/api/user_selection_handler.gointernal/api/validator_test.gointernal/auth/interceptor.gointernal/auth/interceptor_test.gointernal/auth/jwt.gointernal/auth/reconcile_test.gointernal/config/config.gointernal/connection/manager.gointernal/gateway/registry/registry.gointernal/gateway/task_handlers.gointernal/gateway/task_handlers_test.gointernal/handler/terminal_bridge.gointernal/idp/linker.gointernal/idp/linker_test.gointernal/idp/oidc.gointernal/middleware/middleware_test.gointernal/mtls/mtls.gointernal/mtls/peer_class_test.gointernal/projectors/identity_provider_test.gointernal/projectors/luks_key_listener.gointernal/projectors/luks_key_test.gointernal/projectors/role_test.gointernal/projectors/scim_group_mapping.gointernal/projectors/scim_group_mapping_test.gointernal/projectors/security_alert_test.gointernal/projectors/token.gointernal/projectors/token_test.gointernal/projectors/user_role_test.gointernal/projectors/user_selection_listener.gointernal/resolution/resolve_tree_test.gointernal/scim/discovery.gointernal/scim/groups.gointernal/scim/handler.gointernal/scim/scim.gointernal/search/index.gointernal/store/store.gointernal/taskqueue/client.gointernal/taskqueue/payloads.gointernal/taskqueue/types_test.gointernal/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
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.github/workflows/lint.yml (1)
60-61: ⚡ Quick winConsider 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
📒 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.
* 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.
* 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
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
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.
package table. The package was never created — README claimed
it under the deferred GitHub-Releases-polling design.
`internal/asynqutil`, `internal/projectors`. Reorder
alphabetically; move `internal/gateway/registry` next to
`internal/gateway`.
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`.
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).
`internal/api/action_handler.go:33`. Now describes the
current invariant: aqClient is nil when CONTROL_VALKEY_ADDR
is unset.
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
Summary by CodeRabbit
Documentation
Improvements