feat(security): exclude hidden/masked columns from create and update payloads

This commit is contained in:
2026-09-30 23:48:09 +02:00
parent a65ca5f5ce
commit b35399fdfa
13 changed files with 456 additions and 4 deletions
+3
View File
@@ -596,6 +596,9 @@ func (h *Handler) executeUpdate(ctx context.Context, schema, entity, id string,
Data: updates,
Tx: h.db,
}
if err := h.hooks.Execute(BeforeHandle, hookCtx); err != nil {
return nil, err
}
var updateResult interface{}
err = h.runInTx(ctx, hookCtx, func(tx common.Database) error {
+23
View File
@@ -36,6 +36,21 @@ func RegisterSecurityHooks(handler *Handler, securityList *security.SecurityList
return nil
})
// BeforeHandle: preload column rules for writes before the handler opens its
// transaction; the write hooks below only read the cache.
handler.Hooks().Register(BeforeHandle, func(hookCtx *HookContext) error {
return security.PreloadSecurityRules(newSecurityContext(hookCtx), securityList, hookCtx.Operation)
})
// BeforeCreate/BeforeUpdate: drop columns hidden or masked for the user from the
// write payload, so they cannot be inserted or updated.
handler.Hooks().Register(BeforeCreate, func(hookCtx *HookContext) error {
return security.ApplyWriteColumnSecurity(newSecurityContext(hookCtx), securityList)
})
handler.Hooks().Register(BeforeUpdate, func(hookCtx *HookContext) error {
return security.ApplyWriteColumnSecurity(newSecurityContext(hookCtx), securityList)
})
// BeforeRead (1st): load RLS + CLS rules from the provider into SecurityList.
handler.Hooks().Register(BeforeRead, func(hookCtx *HookContext) error {
return security.LoadSecurityRules(newSecurityContext(hookCtx), securityList)
@@ -123,6 +138,14 @@ func (s *securityContext) SetQuery(query interface{}) {
}
}
func (s *securityContext) GetData() interface{} {
return s.ctx.Data
}
func (s *securityContext) SetData(data interface{}) {
s.ctx.Data = data
}
func (s *securityContext) GetResult() interface{} {
return s.ctx.Result
}
+5
View File
@@ -293,6 +293,10 @@ func TestOnTxBeginErrorRollsBackEveryOperation(t *testing.T) {
type stubProvider struct{ security.SecurityProvider }
func (stubProvider) GetColumnSecurity(context.Context, int, string, string) ([]security.ColumnSecurity, error) {
return nil, nil
}
func TestSecurityHooksStampTxSettingsOnEveryTransaction(t *testing.T) {
h, mock, ctx := newTxHarness(t)
list, err := security.NewSecurityList(stubProvider{})
@@ -304,6 +308,7 @@ func TestSecurityHooksStampTxSettingsOnEveryTransaction(t *testing.T) {
})
RegisterSecurityHooks(h, list)
ctx = context.WithValue(ctx, security.UserContextKey, &security.UserContext{UserID: 7, UserName: "u"})
ctx = context.WithValue(ctx, security.UserIDKey, 7)
// Update opens two transactions; each must be stamped before any other SQL.
cols := []string{"id", "name"}