feat(go): durable transaction engine and Kiro IDE 1.0 adapter
Split the monolithic transaction into focused modules: append-only crash-recoverable journal under ~/.elydora/transactions with owner tokens and per-transaction namespaces, staged atomic publication (stage .pending, fsync, rename, fsync), platform-specific atomic replacement (MoveFileEx, renameat2, renamex_np), private-directory hardening per OS, lock-guarded recovery, and workspace cleanup that removes only transaction-created empty directories. Add the Kiro IDE 1.0 plugin: strict v1 hook schema with one enabled PreToolUse guard and PostToolUse audit pair bound to the active workspace, agent-to-workspace identity binding in runtime config, and exact migration of the legacy elydora-audit.kiro.hook contract. Gates: go test ./... and -race pass, go vet clean, govulncheck reports zero reachable vulnerabilities, git diff --check clean.
This commit is contained in:
1 parent
dd72793cdb
commit
eb41b991bf
70 files changed
+8182
-358
No files matched your search
@@ -0,0 +1,228 @@
|
||||
//go:build windows
|
||||
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const transactionFileAllAccess windows.ACCESS_MASK = 0x1f01ff
|
||||
|
||||
const transactionParentMutationAccess windows.ACCESS_MASK = windows.FILE_WRITE_DATA |
|
||||
windows.FILE_APPEND_DATA |
|
||||
windows.DELETE |
|
||||
windows.WRITE_DAC |
|
||||
windows.WRITE_OWNER |
|
||||
windows.GENERIC_WRITE |
|
||||
windows.GENERIC_ALL |
|
||||
0x00000040 // FILE_DELETE_CHILD
|
||||
|
||||
func createPrivateTransactionDirectory(path string) error {
|
||||
descriptor, _, err := privateTransactionSecurityDescriptor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pointer, err := windows.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attributes := windows.SecurityAttributes{
|
||||
Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})),
|
||||
SecurityDescriptor: descriptor,
|
||||
}
|
||||
if err := windows.CreateDirectory(pointer, &attributes); err != nil {
|
||||
return err
|
||||
}
|
||||
return verifyPrivateTransactionDirectory(path)
|
||||
}
|
||||
|
||||
func ensurePrivateTransactionDirectory(path string) error {
|
||||
info, err := os.Lstat(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
if createErr := createPrivateTransactionDirectory(path); createErr == nil {
|
||||
return nil
|
||||
} else if !errors.Is(createErr, os.ErrExist) &&
|
||||
!errors.Is(createErr, windows.ERROR_ALREADY_EXISTS) {
|
||||
return createErr
|
||||
}
|
||||
info, err = os.Lstat(path)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||
return fmt.Errorf("transaction namespace is not a physical directory: %s", path)
|
||||
}
|
||||
descriptor, user, err := privateTransactionSecurityDescriptor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dacl, _, err := descriptor.DACL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := windows.SetNamedSecurityInfo(
|
||||
path,
|
||||
windows.SE_FILE_OBJECT,
|
||||
windows.OWNER_SECURITY_INFORMATION|
|
||||
windows.DACL_SECURITY_INFORMATION|
|
||||
windows.PROTECTED_DACL_SECURITY_INFORMATION,
|
||||
user,
|
||||
nil,
|
||||
dacl,
|
||||
nil,
|
||||
); err != nil {
|
||||
return fmt.Errorf("protect transaction namespace %s: %w", path, err)
|
||||
}
|
||||
return verifyPrivateTransactionDirectory(path)
|
||||
}
|
||||
|
||||
func privateTransactionSecurityDescriptor() (*windows.SECURITY_DESCRIPTOR, *windows.SID, error) {
|
||||
user, err := windows.GetCurrentProcessToken().GetTokenUser()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sid, err := user.User.Sid.Copy()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sidText := sid.String()
|
||||
if sidText == "" {
|
||||
return nil, nil, fmt.Errorf("resolve current Windows user SID")
|
||||
}
|
||||
descriptor, err := windows.SecurityDescriptorFromString(
|
||||
"O:" + sidText + "D:P(A;OICI;FA;;;" + sidText + ")",
|
||||
)
|
||||
return descriptor, sid, err
|
||||
}
|
||||
|
||||
func verifyPrivateTransactionDirectory(path string) error {
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||
return fmt.Errorf("transaction namespace is not a physical directory: %s", path)
|
||||
}
|
||||
user, err := windows.GetCurrentProcessToken().GetTokenUser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
descriptor, err := windows.GetNamedSecurityInfo(
|
||||
path,
|
||||
windows.SE_FILE_OBJECT,
|
||||
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inspect transaction namespace DACL at %s: %w", path, err)
|
||||
}
|
||||
owner, _, err := descriptor.Owner()
|
||||
if err != nil || owner == nil || !owner.Equals(user.User.Sid) {
|
||||
return errors.Join(fmt.Errorf("transaction namespace owner changed: %s", path), err)
|
||||
}
|
||||
control, _, err := descriptor.Control()
|
||||
if err != nil || control&windows.SE_DACL_PROTECTED == 0 {
|
||||
return errors.Join(fmt.Errorf("transaction namespace DACL inherits access: %s", path), err)
|
||||
}
|
||||
dacl, _, err := descriptor.DACL()
|
||||
if err != nil || dacl == nil || dacl.AceCount != 1 {
|
||||
return errors.Join(fmt.Errorf("transaction namespace DACL is not owner-only: %s", path), err)
|
||||
}
|
||||
var ace *windows.ACCESS_ALLOWED_ACE
|
||||
if err := windows.GetAce(dacl, 0, &ace); err != nil || ace == nil {
|
||||
return errors.Join(fmt.Errorf("inspect transaction namespace owner ACE: %s", path), err)
|
||||
}
|
||||
aceSID := (*windows.SID)(unsafe.Pointer(&ace.SidStart))
|
||||
if ace.Header.AceType != windows.ACCESS_ALLOWED_ACE_TYPE ||
|
||||
ace.Header.AceFlags != windows.OBJECT_INHERIT_ACE|windows.CONTAINER_INHERIT_ACE ||
|
||||
ace.Mask != transactionFileAllAccess || !aceSID.Equals(user.User.Sid) {
|
||||
return fmt.Errorf("transaction namespace DACL is not owner-only: %s", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyTransactionNamespaceParent(path string) error {
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil || info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||
return errors.Join(fmt.Errorf("transaction namespace parent is not a physical directory: %s", path), err)
|
||||
}
|
||||
user, err := windows.GetCurrentProcessToken().GetTokenUser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
descriptor, err := windows.GetNamedSecurityInfo(
|
||||
path,
|
||||
windows.SE_FILE_OBJECT,
|
||||
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inspect transaction namespace parent owner at %s: %w", path, err)
|
||||
}
|
||||
owner, _, err := descriptor.Owner()
|
||||
if err != nil || owner == nil || !owner.Equals(user.User.Sid) {
|
||||
return errors.Join(
|
||||
fmt.Errorf("transaction namespace parent owner does not match current user: %s", path),
|
||||
err,
|
||||
)
|
||||
}
|
||||
if err := verifyTransactionNamespaceParentDACL(path, descriptor, user.User.Sid); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyTransactionNamespaceParentDACL(
|
||||
path string,
|
||||
descriptor *windows.SECURITY_DESCRIPTOR,
|
||||
currentUser *windows.SID,
|
||||
) error {
|
||||
dacl, _, err := descriptor.DACL()
|
||||
if err != nil || dacl == nil {
|
||||
return errors.Join(fmt.Errorf("transaction namespace parent has no restrictive DACL: %s", path), err)
|
||||
}
|
||||
system, err := windows.CreateWellKnownSid(windows.WinLocalSystemSid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
administrators, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for index := uint32(0); index < uint32(dacl.AceCount); index++ {
|
||||
var ace *windows.ACCESS_ALLOWED_ACE
|
||||
if err := windows.GetAce(dacl, index, &ace); err != nil || ace == nil {
|
||||
return errors.Join(fmt.Errorf("inspect transaction namespace parent ACE at %s", path), err)
|
||||
}
|
||||
if ace.Header.AceType == windows.ACCESS_DENIED_ACE_TYPE {
|
||||
continue
|
||||
}
|
||||
if ace.Header.AceType != windows.ACCESS_ALLOWED_ACE_TYPE {
|
||||
return fmt.Errorf(
|
||||
"transaction namespace parent has unsupported DACL ACE type %d: %s",
|
||||
ace.Header.AceType,
|
||||
path,
|
||||
)
|
||||
}
|
||||
if ace.Header.AceFlags&windows.INHERIT_ONLY_ACE != 0 ||
|
||||
ace.Mask&transactionParentMutationAccess == 0 {
|
||||
continue
|
||||
}
|
||||
sid := (*windows.SID)(unsafe.Pointer(&ace.SidStart))
|
||||
if !sid.IsValid() {
|
||||
return fmt.Errorf("transaction namespace parent has invalid DACL SID: %s", path)
|
||||
}
|
||||
if !sid.Equals(currentUser) && !sid.Equals(system) && !sid.Equals(administrators) {
|
||||
return fmt.Errorf(
|
||||
"transaction namespace parent grants mutation access to untrusted SID %s: %s",
|
||||
sid.String(),
|
||||
path,
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user