||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

Agent Name Length Validation Bypass in SPIRE httpchallenge Plugin


TL;DR

I found a validation bypass in SPIRE’s httpchallenge node attestor plugin. The 32-character agent name limit wasn’t being enforced due to checking the wrong length variable. Maintainer confirmed it’s a legitimate bug but classified as “Low severity” since exploitation requires specific conditions. PR merged into v1.13.1.

Background

SPIRE (SPIFFE Runtime Environment) is a production-grade identity framework for zero-trust networks. It issues cryptographic identities (SVIDs) to workloads across heterogeneous environments. Used by major companies for service mesh, microservices auth, and workload identity.

The httpchallenge plugin is one of SPIRE’s node attestation methods. During research on authentication bypass vectors, I started looking at input validation in attestation plugins.

Finding the Bug

I was auditing validation functions in attestation plugins. The httpchallenge plugin has a validateAgentName() function that’s supposed to enforce a 32-character limit:

// pkg/server/plugin/nodeattestor/httpchallenge/httpchallenge.go:177
func validateAgentName(agentName string) error {
    l := agentNamePattern.FindAllStringSubmatch(agentName, -1)
    if len(l) != 1 || len(l[0]) == 0 || len(l[0]) > 32 {
        return status.Error(codes.InvalidArgument, "agent name is not valid")
    }
    return nil
}

See the problem? len(l[0]) checks the regex submatch array length (always 1 for this pattern), not the actual agent name string length. The 32-character limit check never triggers.

The Evidence

Testing with a simple Go program:

agentNamePattern := regexp.MustCompile("^[a-zA-z]+[a-zA-Z0-9-]$")

testCases := []string{
    "a1",                                                                           // 2 chars
    "validagentname12345678901",                                                   // 31 chars
    "verylongagentnamethatshouldbetoobigandshouldnotvalidatebutprobablywill",    // 73 chars
}

for _, test := range testCases {
    l := agentNamePattern.FindAllStringSubmatch(test, -1)
    fmt.Printf("Input: %s (len=%d) | len(l[0])=%d | Should pass 32-char check: %t\n", 
        test, len(test), len(l[0]), len(l[0]) <= 32)
}

Output:

Input: a1 (len=2) | len(l[0])=1 | Should pass 32-char check: true
Input: validagentname12345678901 (len=31) | len(l[0])=1 | Should pass 32-char check: true  
Input: verylongagent... (len=73) | len(l[0])=1 | Should pass 32-char check: true

All pass. The 73-character name goes straight through.

Local Testing

Built SPIRE from source and tested with real attestation:

# Build SPIRE
make build

# Configure server with httpchallenge
cat > server.conf << 'EOF'
server {
    bind_address = "127.0.0.1"
    bind_port = "8081"
    trust_domain = "test.local"
    data_dir = "/tmp/spire-test/data/server"
}
plugins {
    DataStore "sql" {
        plugin_data {
            database_type = "sqlite3"
            connection_string = "/tmp/spire-test/data/server/datastore.sqlite3"
        }
    }
    NodeAttestor "http_challenge" {
        plugin_data {
            tofu = false
            allow_non_root_ports = false
        }
    }
    KeyManager "memory" {
        plugin_data = {}
    }
}
EOF

# Start server
./bin/spire-server run -config server.conf &

# Configure agent with 93-character agent name
cat > agent.conf << 'EOF'
agent {
    data_dir = "/tmp/spire-test/data/agent"
    server_address = "127.0.0.1"
    server_port = "8081"
    trust_domain = "test.local"
    trust_bundle_path = "/tmp/spire-test/conf/agent/bootstrap.crt"
}
plugins {
    NodeAttestor "http_challenge" {
        plugin_data {
            hostname = "testhost"
            agentname = "verylongagentnamethatshouldbetoobigandshouldnotvalidatebutprobablywillduetothebugwefound"
            port = 80
        }
    }
    KeyManager "disk" {
        plugin_data {
            directory = "/tmp/spire-test/data/agent"
        }
    }
    WorkloadAttestor "unix" {
        plugin_data {}
    }
}
EOF

# Run agent
./bin/spire-agent run -config agent.conf

Result:

image
DEBU[0000] Setting up nonce handler path=/.well-known/spiffe/nodeattestor/http_challenge/verylongagentnamethatshouldbetoobigandshouldnotvalidatebutprobablywillduetothebugwefound/challenge

The 93-character agent name passed validation and made it into the URL path. Expected to see “agent name is not valid” but validation was completely bypassed.

The Fix

One character change:

image
func validateAgentName(agentName string) error {
    l := agentNamePattern.FindAllStringSubmatch(agentName, -1)
    if len(l) != 1 || len(l[0]) == 0 || len(agentName) > 32 {  // <- changed l[0] to agentName
        return status.Error(codes.InvalidArgument, "agent name is not valid")
    }
    return nil
}

After the fix, tested again - 93-character name correctly rejected:

ERRO[0016] Invalid argument: nodeattestor(http_challenge): agent name is not valid

Disclosure

Reported via email to security@spiffe.io. SPIRE maintainer responded:

image

“Thanks for your interest in contributing a fix. We would welcome a pull request from you… Regarding the CVE identifier and GHSA, after reviewing it, we’ve assessed this as a Low severity issue rather than Medium or High. The agent name is configured in the agent config file, which we treat as a trusted input under the operator’s control. The practical impact is limited as standard web server URL length limits would prevent any meaningful exploitation.”

Fair assessment. The bug is real but exploitation requires the operator to configure an extremely long agent name, which isn’t a typical attack scenario. No CVE assigned per their policy (Medium+ only).

Submitted PR #6324 with the fix.

Conclusion

This validation bypass is real and causes the intended security control to be completely ineffective, but practical exploitation is limited. The maintainers were right to classify it as Low severity since it requires operator-controlled configuration.

What I learned:

  • SPIRE’s node attestation architecture
  • Go regex submatch behavior
  • When “Low severity” is the right call
  • Professional disclosure process

Status: PR #6324 merged into v1.13.1


Timeline:

  • September 16, 2025: Bug discovered during code audit
  • September 16, 2025: Reported to security@spiffe.io
  • September 16, 2025: Maintainer response , “legitimate bug, Low severity”
  • September 17, 2025: PR opened and merged
  • September 18, 2025: Included in v1.13.1 release

Affected: SPIRE versions with httpchallenge plugin Fix: v1.13.1 release Commit: 1e2d9e304364e554de09cff21bfdee321ccba107



← Back to Home