 131868bdca
			
		
	
	131868bdca
	
	
	
		
			
			Major security, observability, and configuration improvements:
## Security Hardening
- Implemented configurable CORS (no more wildcards)
- Added comprehensive auth middleware for admin endpoints
- Enhanced webhook HMAC validation
- Added input validation and rate limiting
- Security headers and CSP policies
## Configuration Management
- Made N8N webhook URL configurable (WHOOSH_N8N_BASE_URL)
- Replaced all hardcoded endpoints with environment variables
- Added feature flags for LLM vs heuristic composition
- Gitea fetch hardening with EAGER_FILTER and FULL_RESCAN options
## API Completeness
- Implemented GetCouncilComposition function
- Added GET /api/v1/councils/{id} endpoint
- Council artifacts API (POST/GET /api/v1/councils/{id}/artifacts)
- /admin/health/details endpoint with component status
- Database lookup for repository URLs (no hardcoded fallbacks)
## Observability & Performance
- Added OpenTelemetry distributed tracing with goal/pulse correlation
- Performance optimization database indexes
- Comprehensive health monitoring
- Enhanced logging and error handling
## Infrastructure
- Production-ready P2P discovery (replaces mock implementation)
- Removed unused Redis configuration
- Enhanced Docker Swarm integration
- Added migration files for performance indexes
## Code Quality
- Comprehensive input validation
- Graceful error handling and failsafe fallbacks
- Backwards compatibility maintained
- Following security best practices
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
		
	
		
			
				
	
	
		
			248 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			248 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package digestset
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	digest "github.com/opencontainers/go-digest"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// ErrDigestNotFound is used when a matching digest
 | |
| 	// could not be found in a set.
 | |
| 	ErrDigestNotFound = errors.New("digest not found")
 | |
| 
 | |
| 	// ErrDigestAmbiguous is used when multiple digests
 | |
| 	// are found in a set. None of the matching digests
 | |
| 	// should be considered valid matches.
 | |
| 	ErrDigestAmbiguous = errors.New("ambiguous digest string")
 | |
| )
 | |
| 
 | |
| // Set is used to hold a unique set of digests which
 | |
| // may be easily referenced by easily  referenced by a string
 | |
| // representation of the digest as well as short representation.
 | |
| // The uniqueness of the short representation is based on other
 | |
| // digests in the set. If digests are omitted from this set,
 | |
| // collisions in a larger set may not be detected, therefore it
 | |
| // is important to always do short representation lookups on
 | |
| // the complete set of digests. To mitigate collisions, an
 | |
| // appropriately long short code should be used.
 | |
| type Set struct {
 | |
| 	mutex   sync.RWMutex
 | |
| 	entries digestEntries
 | |
| }
 | |
| 
 | |
| // NewSet creates an empty set of digests
 | |
| // which may have digests added.
 | |
| func NewSet() *Set {
 | |
| 	return &Set{
 | |
| 		entries: digestEntries{},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // checkShortMatch checks whether two digests match as either whole
 | |
| // values or short values. This function does not test equality,
 | |
| // rather whether the second value could match against the first
 | |
| // value.
 | |
| func checkShortMatch(alg digest.Algorithm, hex, shortAlg, shortHex string) bool {
 | |
| 	if len(hex) == len(shortHex) {
 | |
| 		if hex != shortHex {
 | |
| 			return false
 | |
| 		}
 | |
| 		if len(shortAlg) > 0 && string(alg) != shortAlg {
 | |
| 			return false
 | |
| 		}
 | |
| 	} else if !strings.HasPrefix(hex, shortHex) {
 | |
| 		return false
 | |
| 	} else if len(shortAlg) > 0 && string(alg) != shortAlg {
 | |
| 		return false
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // Lookup looks for a digest matching the given string representation.
 | |
| // If no digests could be found ErrDigestNotFound will be returned
 | |
| // with an empty digest value. If multiple matches are found
 | |
| // ErrDigestAmbiguous will be returned with an empty digest value.
 | |
| func (dst *Set) Lookup(d string) (digest.Digest, error) {
 | |
| 	dst.mutex.RLock()
 | |
| 	defer dst.mutex.RUnlock()
 | |
| 	if len(dst.entries) == 0 {
 | |
| 		return "", ErrDigestNotFound
 | |
| 	}
 | |
| 	var (
 | |
| 		searchFunc func(int) bool
 | |
| 		alg        digest.Algorithm
 | |
| 		hex        string
 | |
| 	)
 | |
| 	dgst, err := digest.Parse(d)
 | |
| 	if err == digest.ErrDigestInvalidFormat {
 | |
| 		hex = d
 | |
| 		searchFunc = func(i int) bool {
 | |
| 			return dst.entries[i].val >= d
 | |
| 		}
 | |
| 	} else {
 | |
| 		hex = dgst.Hex()
 | |
| 		alg = dgst.Algorithm()
 | |
| 		searchFunc = func(i int) bool {
 | |
| 			if dst.entries[i].val == hex {
 | |
| 				return dst.entries[i].alg >= alg
 | |
| 			}
 | |
| 			return dst.entries[i].val >= hex
 | |
| 		}
 | |
| 	}
 | |
| 	idx := sort.Search(len(dst.entries), searchFunc)
 | |
| 	if idx == len(dst.entries) || !checkShortMatch(dst.entries[idx].alg, dst.entries[idx].val, string(alg), hex) {
 | |
| 		return "", ErrDigestNotFound
 | |
| 	}
 | |
| 	if dst.entries[idx].alg == alg && dst.entries[idx].val == hex {
 | |
| 		return dst.entries[idx].digest, nil
 | |
| 	}
 | |
| 	if idx+1 < len(dst.entries) && checkShortMatch(dst.entries[idx+1].alg, dst.entries[idx+1].val, string(alg), hex) {
 | |
| 		return "", ErrDigestAmbiguous
 | |
| 	}
 | |
| 
 | |
| 	return dst.entries[idx].digest, nil
 | |
| }
 | |
| 
 | |
| // Add adds the given digest to the set. An error will be returned
 | |
| // if the given digest is invalid. If the digest already exists in the
 | |
| // set, this operation will be a no-op.
 | |
| func (dst *Set) Add(d digest.Digest) error {
 | |
| 	if err := d.Validate(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	dst.mutex.Lock()
 | |
| 	defer dst.mutex.Unlock()
 | |
| 	entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
 | |
| 	searchFunc := func(i int) bool {
 | |
| 		if dst.entries[i].val == entry.val {
 | |
| 			return dst.entries[i].alg >= entry.alg
 | |
| 		}
 | |
| 		return dst.entries[i].val >= entry.val
 | |
| 	}
 | |
| 	idx := sort.Search(len(dst.entries), searchFunc)
 | |
| 	if idx == len(dst.entries) {
 | |
| 		dst.entries = append(dst.entries, entry)
 | |
| 		return nil
 | |
| 	} else if dst.entries[idx].digest == d {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	entries := append(dst.entries, nil)
 | |
| 	copy(entries[idx+1:], entries[idx:len(entries)-1])
 | |
| 	entries[idx] = entry
 | |
| 	dst.entries = entries
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Remove removes the given digest from the set. An err will be
 | |
| // returned if the given digest is invalid. If the digest does
 | |
| // not exist in the set, this operation will be a no-op.
 | |
| func (dst *Set) Remove(d digest.Digest) error {
 | |
| 	if err := d.Validate(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	dst.mutex.Lock()
 | |
| 	defer dst.mutex.Unlock()
 | |
| 	entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
 | |
| 	searchFunc := func(i int) bool {
 | |
| 		if dst.entries[i].val == entry.val {
 | |
| 			return dst.entries[i].alg >= entry.alg
 | |
| 		}
 | |
| 		return dst.entries[i].val >= entry.val
 | |
| 	}
 | |
| 	idx := sort.Search(len(dst.entries), searchFunc)
 | |
| 	// Not found if idx is after or value at idx is not digest
 | |
| 	if idx == len(dst.entries) || dst.entries[idx].digest != d {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	entries := dst.entries
 | |
| 	copy(entries[idx:], entries[idx+1:])
 | |
| 	entries = entries[:len(entries)-1]
 | |
| 	dst.entries = entries
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // All returns all the digests in the set
 | |
| func (dst *Set) All() []digest.Digest {
 | |
| 	dst.mutex.RLock()
 | |
| 	defer dst.mutex.RUnlock()
 | |
| 	retValues := make([]digest.Digest, len(dst.entries))
 | |
| 	for i := range dst.entries {
 | |
| 		retValues[i] = dst.entries[i].digest
 | |
| 	}
 | |
| 
 | |
| 	return retValues
 | |
| }
 | |
| 
 | |
| // ShortCodeTable returns a map of Digest to unique short codes. The
 | |
| // length represents the minimum value, the maximum length may be the
 | |
| // entire value of digest if uniqueness cannot be achieved without the
 | |
| // full value. This function will attempt to make short codes as short
 | |
| // as possible to be unique.
 | |
| func ShortCodeTable(dst *Set, length int) map[digest.Digest]string {
 | |
| 	dst.mutex.RLock()
 | |
| 	defer dst.mutex.RUnlock()
 | |
| 	m := make(map[digest.Digest]string, len(dst.entries))
 | |
| 	l := length
 | |
| 	resetIdx := 0
 | |
| 	for i := 0; i < len(dst.entries); i++ {
 | |
| 		var short string
 | |
| 		extended := true
 | |
| 		for extended {
 | |
| 			extended = false
 | |
| 			if len(dst.entries[i].val) <= l {
 | |
| 				short = dst.entries[i].digest.String()
 | |
| 			} else {
 | |
| 				short = dst.entries[i].val[:l]
 | |
| 				for j := i + 1; j < len(dst.entries); j++ {
 | |
| 					if checkShortMatch(dst.entries[j].alg, dst.entries[j].val, "", short) {
 | |
| 						if j > resetIdx {
 | |
| 							resetIdx = j
 | |
| 						}
 | |
| 						extended = true
 | |
| 					} else {
 | |
| 						break
 | |
| 					}
 | |
| 				}
 | |
| 				if extended {
 | |
| 					l++
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		m[dst.entries[i].digest] = short
 | |
| 		if i >= resetIdx {
 | |
| 			l = length
 | |
| 		}
 | |
| 	}
 | |
| 	return m
 | |
| }
 | |
| 
 | |
| type digestEntry struct {
 | |
| 	alg    digest.Algorithm
 | |
| 	val    string
 | |
| 	digest digest.Digest
 | |
| }
 | |
| 
 | |
| type digestEntries []*digestEntry
 | |
| 
 | |
| func (d digestEntries) Len() int {
 | |
| 	return len(d)
 | |
| }
 | |
| 
 | |
| func (d digestEntries) Less(i, j int) bool {
 | |
| 	if d[i].val != d[j].val {
 | |
| 		return d[i].val < d[j].val
 | |
| 	}
 | |
| 	return d[i].alg < d[j].alg
 | |
| }
 | |
| 
 | |
| func (d digestEntries) Swap(i, j int) {
 | |
| 	d[i], d[j] = d[j], d[i]
 | |
| }
 |