-
Notifications
You must be signed in to change notification settings - Fork 0
CI: Bump macOS runner to macos-26-intel #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4f749dd
be09ecb
6957f1e
2c6eefa
cc73520
a6e1c27
ca8cdfc
e940fbd
19d0137
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @preconcurrency import OpenTelemetryApi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+2
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Shared W3C Trace Context parsing and serialization. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Used by both `OTelTracingBridge` (for gRPC distributed tracing) and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// `SignozTracingMiddleware` (for Vapor HTTP tracing). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public enum W3CTraceContext { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static let traceparentKey = "traceparent" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static let tracestateKey = "tracestate" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Parse W3C `traceparent` and optional `tracestate` header values into a `SpanContext`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static func parse(traceparent: String, tracestate: String? = nil) -> SpanContext? { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // W3C spec: reject only version "ff"; accept unknown future versions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // for forward compatibility (the first 55 chars are guaranteed stable). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Future versions may append extra `-` delimited fields, so require >= 4 parts. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+16
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let parts = traceparent.split(separator: "-") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard parts.count >= 4, parts[0] != "ff" else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let traceId = TraceId(fromHexString: String(parts[1])) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let spanId = SpanId(fromHexString: String(parts[2])) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard traceId.isValid, spanId.isValid else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let sampled = UInt8(String(parts[3]), radix: 16).map { $0 & 0x01 != 0 } ?? false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+28
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard parts.count >= 4, parts[0] != "ff" else { | |
| return nil | |
| } | |
| let traceId = TraceId(fromHexString: String(parts[1])) | |
| let spanId = SpanId(fromHexString: String(parts[2])) | |
| guard traceId.isValid, spanId.isValid else { | |
| return nil | |
| } | |
| let sampled = UInt8(String(parts[3]), radix: 16).map { $0 & 0x01 != 0 } ?? false | |
| // Need at least 4 parts: version, trace-id, span-id, trace-flags. | |
| guard parts.count >= 4 else { | |
| return nil | |
| } | |
| let versionPart = parts[0] | |
| // Version field must be exactly 2 hex chars and not "ff". | |
| guard versionPart.count == 2, | |
| UInt8(versionPart, radix: 16) != nil, | |
| versionPart != "ff" | |
| else { | |
| return nil | |
| } | |
| // For version "00", the header must have exactly 4 parts (no extras). | |
| if versionPart == "00" && parts.count != 4 { | |
| return nil | |
| } | |
| let traceId = TraceId(fromHexString: String(parts[1])) | |
| let spanId = SpanId(fromHexString: String(parts[2])) | |
| guard traceId.isValid, spanId.isValid else { | |
| return nil | |
| } | |
| let flagsPart = parts[3] | |
| // Trace-flags field must be exactly 2 hex chars. | |
| guard flagsPart.count == 2, | |
| let flagsByte = UInt8(flagsPart, radix: 16) | |
| else { | |
| return nil | |
| } | |
| let sampled = (flagsByte & 0x01) != 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§© Analysis chain
π Script executed:
cat -n Sources/SignozSwift/Tracing/W3CTraceContext.swiftRepository: photon-hq/SignozSwift
Length of output: 3244
π Web query:
W3C Trace Context specification traceparent header format validation requirements
π‘ Result:
traceparent format (Trace Context v1.0, version 00)
A traceparent header value must be (ABNF):
version "-" trace-id "-" parent-id "-" trace-flags, where all hex is lowercase (0-9a-f). [1]
- version: exactly 2 lowercase hex chars;
ffis forbidden/invalid. [1] - trace-id: exactly 32 lowercase hex chars (16 bytes); all-zero value is invalid (
000β¦000). If invalid, vendors MUST ignore thetraceparent. [1] - parent-id: exactly 16 lowercase hex chars (8 bytes); all-zero value is invalid (
000β¦0000). If invalid, vendors MUST ignore thetraceparent. [1] - trace-flags: exactly 2 lowercase hex chars (1 byte). For version
00, only the least-significant bit is defined (sampled); all other bits are reserved and vendors MUST set them to0when propagating. [1]
What to do when parsing/validation fails
- If the version prefix canβt be parsed (not
2 hex+-), the implementation should restart the trace (i.e., create a newtraceparent) and deletetracestate. [1] - If the version is higher than supported, the implementation SHOULD try to parse
trace-id,parent-id, and the sampled bit using fixed positions/checks; if that parsing fails, restart the trace and deletetracestate; also set any unknown/unparsed flags to 0 on outgoing requests. [1] - If
traceparentparsing fails, the vendor MUST NOT attempt to parsetracestate. [1]
Header name casing
- Senders SHOULD use lowercase
traceparent, but receivers MUST accept any case. [1]
Sources
[1] W3C Recommendation: Trace Context (Trace Context HTTP Headers Format / traceparent). (w3.org)
Citations:
π Script executed:
rg -t swift "class TraceId|struct TraceId" --max-count=5 -A 10 -B 2Repository: photon-hq/SignozSwift
Length of output: 47
π Script executed:
rg "TraceId|SpanId" --max-count=10 -B 2 -A 5 | head -50Repository: photon-hq/SignozSwift
Length of output: 1640
π Script executed:
rg "import.*TraceId|import.*OpenTelemetry" --max-count=10Repository: photon-hq/SignozSwift
Length of output: 2068
π Script executed:
# Check if there are any tests for W3CTraceContext.parse to understand current behavior
rg "W3CTraceContext.parse|traceparent" -A 5 -B 2 Tests/Repository: photon-hq/SignozSwift
Length of output: 2081
π Script executed:
rg "Invalid traceparent|sampledBitmask" -A 10 Tests/SignozSwiftTests/SignozSwiftTests.swiftRepository: photon-hq/SignozSwift
Length of output: 777
π Script executed:
# Check if TraceId and SpanId validate all-zeros case
rg "isValid|all.zero|00000000" Tests/SignozSwiftTests/ | grep -i "trace\|span" | head -20Repository: photon-hq/SignozSwift
Length of output: 47
π Script executed:
# Check the test for sampledBitmask to see if uppercase hex is tested
rg "traceparent.*03|traceparent.*[A-F]" Tests/Repository: photon-hq/SignozSwift
Length of output: 196
π Script executed:
# Look for any test cases with invalid flags or malformed traceparent
rg "@Test|func.*traceparent|func.*trace" Tests/SignozSwiftTests/SignozSwiftTests.swift -A 1 | grep -A 1 "@Test"Repository: photon-hq/SignozSwift
Length of output: 3905
π Script executed:
# Let's verify what happens with invalid hex in the flags field
cat > /tmp/test_parse.swift << 'EOF'
let flagsInvalid = "ZZ"
let result = UInt8(flagsInvalid, radix: 16)
print("Invalid hex 'ZZ': \(result ?? -1)") // Should be nil
let flagsValid = "03"
let result2 = UInt8(flagsValid, radix: 16)
print("Valid hex '03': \(result2 ?? -1)") // Should be 3
// Current implementation pattern
let sampled = UInt8(flagsInvalid, radix: 16).map { $0 & 0x01 != 0 } ?? false
print("Invalid flags default to sampled=false: \(sampled)")
EOF
# Can't run Swift directly, but we can verify the logic is correct
cat /tmp/test_parse.swiftRepository: photon-hq/SignozSwift
Length of output: 487
π Script executed:
# Check if there's any validation of hex character length or format in the tests
rg "parts\[0\]|parts\[3\]|parts\.count" Sources/SignozSwift/Tracing/W3CTraceContext.swiftRepository: photon-hq/SignozSwift
Length of output: 209
Strengthen traceparent validation to comply with W3C Trace Context specification.
Line 18 uses string comparison (!= "ff") instead of hex validation, so it accepts uppercase variants like "FF" and doesn't reject invalid hex. Line 28 silently converts invalid flags to false instead of rejecting the header. The W3C spec requires exact 2-character hex fields with specific constraints, and vendors must ignore malformed traceparent headers entirely. Currently, invalid headers may still produce a SpanContext and propagate as remote parents downstream.
Suggested hardening
- let parts = traceparent.split(separator: "-")
- guard parts.count >= 4, parts[0] != "ff" else {
+ let parts = traceparent.split(separator: "-", omittingEmptySubsequences: false)
+ guard parts.count >= 4,
+ parts[0].count == 2,
+ let version = UInt8(String(parts[0]), radix: 16),
+ version != 0xff,
+ parts[3].count == 2,
+ let flags = UInt8(String(parts[3]), radix: 16),
+ !(version == 0x00 && parts.count != 4) else {
return nil
}
let traceId = TraceId(fromHexString: String(parts[1]))
let spanId = SpanId(fromHexString: String(parts[2]))
guard traceId.isValid, spanId.isValid else {
return nil
}
- let sampled = UInt8(String(parts[3]), radix: 16).map { $0 & 0x01 != 0 } ?? false
+ let sampled = flags & 0x01 != 0π€ Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Sources/SignozSwift/Tracing/W3CTraceContext.swift` around lines 13 - 30, In
parse(traceparent:tracestate:) strengthen validation: require parts.count >= 4
and validate each field's length and hex-ness (parts[0] must be exactly 2 hex
chars and not "ff" case-insensitive; parts[1] must be 32 hex chars for TraceId;
parts[2] must be 16 hex chars for SpanId; parts[3] must be exactly 2 hex chars
for flags), and if any of these checks fail return nil; parse the flags with a
failable UInt8 initializer and if it fails reject the header instead of
defaulting to false, and only construct/set traceFlags (using
TraceFlags.setIsSampled) after all validations succeed (use
TraceId(fromHexString:) and SpanId(fromHexString:) results only when their
inputs passed the explicit hex/length checks).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR metadata says this change is only a CI runner bump, but this PR also adds a new
SignozVaporproduct/target, a new middleware implementation, W3C trace context refactor, and documentation updates. Please update the PR title/description (or split into separate PRs) so reviewers understand the full scope.