 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>
		
	
		
			
				
	
	
		
			153 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			153 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2014 Alvaro J. Genial. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package form
 | |
| 
 | |
| import (
 | |
| 	"net/url"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| type node map[string]interface{}
 | |
| 
 | |
| func (n node) values(d, e rune) url.Values {
 | |
| 	vs := url.Values{}
 | |
| 	n.merge(d, e, "", &vs)
 | |
| 	return vs
 | |
| }
 | |
| 
 | |
| func (n node) merge(d, e rune, p string, vs *url.Values) {
 | |
| 	for k, x := range n {
 | |
| 		switch y := x.(type) {
 | |
| 		case string:
 | |
| 			vs.Add(p+escape(d, e, k), y)
 | |
| 		case node:
 | |
| 			y.merge(d, e, p+escape(d, e, k)+string(d), vs)
 | |
| 		default:
 | |
| 			panic("value is neither string nor node")
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TODO: Add tests for implicit indexing.
 | |
| func parseValues(d, e rune, vs url.Values, canIndexFirstLevelOrdinally bool) node {
 | |
| 	// NOTE: Because of the flattening of potentially multiple strings to one key, implicit indexing works:
 | |
| 	//    i. At the first level;   e.g. Foo.Bar=A&Foo.Bar=B     becomes 0.Foo.Bar=A&1.Foo.Bar=B
 | |
| 	//   ii. At the last level;    e.g. Foo.Bar._=A&Foo.Bar._=B becomes Foo.Bar.0=A&Foo.Bar.1=B
 | |
| 	// TODO: At in-between levels; e.g. Foo._.Bar=A&Foo._.Bar=B becomes Foo.0.Bar=A&Foo.1.Bar=B
 | |
| 	//       (This last one requires that there only be one placeholder in order for it to be unambiguous.)
 | |
| 
 | |
| 	m := map[string]string{}
 | |
| 	for k, ss := range vs {
 | |
| 		indexLastLevelOrdinally := strings.HasSuffix(k, string(d)+implicitKey)
 | |
| 
 | |
| 		for i, s := range ss {
 | |
| 			if canIndexFirstLevelOrdinally {
 | |
| 				k = strconv.Itoa(i) + string(d) + k
 | |
| 			} else if indexLastLevelOrdinally {
 | |
| 				k = strings.TrimSuffix(k, implicitKey) + strconv.Itoa(i)
 | |
| 			}
 | |
| 
 | |
| 			m[k] = s
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	n := node{}
 | |
| 	for k, s := range m {
 | |
| 		n = n.split(d, e, k, s)
 | |
| 	}
 | |
| 	return n
 | |
| }
 | |
| 
 | |
| func splitPath(d, e rune, path string) (k, rest string) {
 | |
| 	esc := false
 | |
| 	for i, r := range path {
 | |
| 		switch {
 | |
| 		case !esc && r == e:
 | |
| 			esc = true
 | |
| 		case !esc && r == d:
 | |
| 			return unescape(d, e, path[:i]), path[i+1:]
 | |
| 		default:
 | |
| 			esc = false
 | |
| 		}
 | |
| 	}
 | |
| 	return unescape(d, e, path), ""
 | |
| }
 | |
| 
 | |
| func (n node) split(d, e rune, path, s string) node {
 | |
| 	k, rest := splitPath(d, e, path)
 | |
| 	if rest == "" {
 | |
| 		return add(n, k, s)
 | |
| 	}
 | |
| 	if _, ok := n[k]; !ok {
 | |
| 		n[k] = node{}
 | |
| 	}
 | |
| 
 | |
| 	c := getNode(n[k])
 | |
| 	n[k] = c.split(d, e, rest, s)
 | |
| 	return n
 | |
| }
 | |
| 
 | |
| func add(n node, k, s string) node {
 | |
| 	if n == nil {
 | |
| 		return node{k: s}
 | |
| 	}
 | |
| 
 | |
| 	if _, ok := n[k]; ok {
 | |
| 		panic("key " + k + " already set")
 | |
| 	}
 | |
| 
 | |
| 	n[k] = s
 | |
| 	return n
 | |
| }
 | |
| 
 | |
| func isEmpty(x interface{}) bool {
 | |
| 	switch y := x.(type) {
 | |
| 	case string:
 | |
| 		return y == ""
 | |
| 	case node:
 | |
| 		if s, ok := y[""].(string); ok {
 | |
| 			return s == ""
 | |
| 		}
 | |
| 		return false
 | |
| 	}
 | |
| 	panic("value is neither string nor node")
 | |
| }
 | |
| 
 | |
| func getNode(x interface{}) node {
 | |
| 	switch y := x.(type) {
 | |
| 	case string:
 | |
| 		return node{"": y}
 | |
| 	case node:
 | |
| 		return y
 | |
| 	}
 | |
| 	panic("value is neither string nor node")
 | |
| }
 | |
| 
 | |
| func getString(x interface{}) string {
 | |
| 	switch y := x.(type) {
 | |
| 	case string:
 | |
| 		return y
 | |
| 	case node:
 | |
| 		if s, ok := y[""].(string); ok {
 | |
| 			return s
 | |
| 		}
 | |
| 		return ""
 | |
| 	}
 | |
| 	panic("value is neither string nor node")
 | |
| }
 | |
| 
 | |
| func escape(d, e rune, s string) string {
 | |
| 	s = strings.Replace(s, string(e), string(e)+string(e), -1) // Escape the escape    (\ => \\)
 | |
| 	s = strings.Replace(s, string(d), string(e)+string(d), -1) // Escape the delimiter (. => \.)
 | |
| 	return s
 | |
| }
 | |
| 
 | |
| func unescape(d, e rune, s string) string {
 | |
| 	s = strings.Replace(s, string(e)+string(d), string(d), -1) // Unescape the delimiter (\. => .)
 | |
| 	s = strings.Replace(s, string(e)+string(e), string(e), -1) // Unescape the escape    (\\ => \)
 | |
| 	return s
 | |
| }
 |