Stabilize iOS/watchOS/tvOS apps and add cross-platform audit remediation

This commit is contained in:
Trey t
2026-02-11 12:54:40 -06:00
parent e40275e694
commit acce712261
77 changed files with 2940 additions and 765 deletions

View File

@@ -0,0 +1,6 @@
import Foundation
public enum AppNotifications {
public static let createdNewWorkout = Notification.Name("CreatedNewWorkout")
}

View File

@@ -0,0 +1,40 @@
import Foundation
public struct BoundedFIFOQueue<Element> {
private var storage: [Element] = []
public let maxCount: Int
public init(maxCount: Int) {
self.maxCount = max(1, maxCount)
}
public var count: Int {
storage.count
}
public var isEmpty: Bool {
storage.isEmpty
}
@discardableResult
public mutating func enqueue(_ element: Element) -> Int {
var droppedCount = 0
if storage.count >= maxCount {
droppedCount = storage.count - maxCount + 1
storage.removeFirst(droppedCount)
}
storage.append(element)
return droppedCount
}
public mutating func dequeueAll() -> [Element] {
let elements = storage
storage.removeAll()
return elements
}
public mutating func clear() {
storage.removeAll()
}
}

View File

@@ -0,0 +1,76 @@
import Foundation
import os
public enum RuntimeSeverity: String, Sendable {
case info
case warning
case error
}
public struct RuntimeEvent: Sendable {
public let severity: RuntimeSeverity
public let message: String
public let metadata: [String: String]
public let timestamp: Date
public init(severity: RuntimeSeverity, message: String, metadata: [String: String], timestamp: Date = Date()) {
self.severity = severity
self.message = message
self.metadata = metadata
self.timestamp = timestamp
}
}
public final class RuntimeReporter {
public typealias Sink = @Sendable (RuntimeEvent) -> Void
public static let shared = RuntimeReporter()
private let logger = Logger(subsystem: "com.werkout.sharedcore", category: "runtime")
private let lock = NSLock()
private var sink: Sink?
private init() {}
public func setSink(_ sink: Sink?) {
lock.lock()
self.sink = sink
lock.unlock()
}
public func recordError(_ message: String, metadata: [String: String] = [:]) {
record(.error, message: message, metadata: metadata)
}
public func recordWarning(_ message: String, metadata: [String: String] = [:]) {
record(.warning, message: message, metadata: metadata)
}
public func recordInfo(_ message: String, metadata: [String: String] = [:]) {
record(.info, message: message, metadata: metadata)
}
private func record(_ severity: RuntimeSeverity, message: String, metadata: [String: String]) {
let flattenedMetadata = metadata
.map { "\($0.key)=\($0.value)" }
.sorted()
.joined(separator: ",")
let logMessage = flattenedMetadata.isEmpty ? message : "\(message) | \(flattenedMetadata)"
switch severity {
case .info:
logger.info("\(logMessage, privacy: .public)")
case .warning:
logger.warning("\(logMessage, privacy: .public)")
case .error:
logger.error("\(logMessage, privacy: .public)")
}
lock.lock()
let sink = self.sink
lock.unlock()
sink?(RuntimeEvent(severity: severity, message: message, metadata: metadata))
}
}

View File

@@ -0,0 +1,103 @@
import Foundation
public enum TokenSecurity {
public static let defaultRotationWindow: TimeInterval = 60 * 60
private static let authPrefixes = ["token", "bearer"]
// Basic high-entropy hex token detector for accidental commits.
private static let tokenRegex = try? NSRegularExpression(pattern: "\\b[a-fA-F0-9]{32,}\\b")
public static func containsPotentialHardcodedToken(in text: String) -> Bool {
guard let tokenRegex else {
return false
}
let range = NSRange(location: 0, length: text.utf16.count)
return tokenRegex.firstMatch(in: text, options: [], range: range) != nil
}
public static func isRedactedToken(_ token: String?) -> Bool {
guard let token else { return false }
let upper = token.uppercased()
return upper.contains("REDACTED") || upper.contains("YOUR_TOKEN") || upper.contains("PLACEHOLDER")
}
public static func sanitizeToken(_ token: String?) -> String? {
guard let rawToken = token?.trimmingCharacters(in: .whitespacesAndNewlines),
rawToken.isEmpty == false else {
return nil
}
let normalized = normalizeAuthPrefix(rawToken)
guard normalized.isEmpty == false,
isRedactedToken(normalized) == false else {
return nil
}
return normalized
}
public static func jwtExpiration(_ token: String) -> Date? {
let segments = token.split(separator: ".")
guard segments.count == 3 else {
return nil
}
let payloadSegment = String(segments[1])
guard let payloadData = base64URLDecode(payloadSegment),
let object = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
let exp = object["exp"] as? TimeInterval else {
return nil
}
return Date(timeIntervalSince1970: exp)
}
public static func isExpired(_ token: String?, now: Date = Date()) -> Bool {
guard let token = sanitizeToken(token) else {
return true
}
guard let expiration = jwtExpiration(token) else {
// Non-JWT tokens cannot be locally validated for expiry.
return false
}
return expiration <= now
}
public static func shouldRotate(_ token: String?, now: Date = Date(), rotationWindow: TimeInterval = defaultRotationWindow) -> Bool {
guard let token = sanitizeToken(token),
let expiration = jwtExpiration(token) else {
return false
}
return expiration.timeIntervalSince(now) <= rotationWindow
}
private static func base64URLDecode(_ input: String) -> Data? {
var value = input
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
let remainder = value.count % 4
if remainder > 0 {
value.append(String(repeating: "=", count: 4 - remainder))
}
return Data(base64Encoded: value)
}
private static func normalizeAuthPrefix(_ token: String) -> String {
let lowercased = token.lowercased()
for prefix in authPrefixes {
if lowercased == prefix {
return ""
}
let prefixed = "\(prefix) "
if lowercased.hasPrefix(prefixed) {
return String(token.dropFirst(prefixed.count)).trimmingCharacters(in: .whitespacesAndNewlines)
}
}
return token
}
}

View File

@@ -0,0 +1,40 @@
import Foundation
public enum WatchPayloadValidationError: Error, Equatable {
case emptyPayload
case payloadTooLarge(actualBytes: Int, maxBytes: Int)
case decodeFailure
}
public enum WatchPayloadValidation {
public static let defaultMaxPayloadBytes = 256 * 1024
public static func validate(_ payload: Data, maxBytes: Int = defaultMaxPayloadBytes) -> WatchPayloadValidationError? {
guard payload.isEmpty == false else {
return .emptyPayload
}
guard payload.count <= maxBytes else {
return .payloadTooLarge(actualBytes: payload.count, maxBytes: maxBytes)
}
return nil
}
public static func decode<T: Decodable>(
_ type: T.Type,
from payload: Data,
maxBytes: Int = defaultMaxPayloadBytes,
decoder: JSONDecoder = JSONDecoder()
) throws -> T {
if let validationError = validate(payload, maxBytes: maxBytes) {
throw validationError
}
do {
return try decoder.decode(T.self, from: payload)
} catch {
throw WatchPayloadValidationError.decodeFailure
}
}
}

View File

@@ -0,0 +1,50 @@
import Foundation
public struct WorkoutValidationIssue: Equatable {
public let code: String
public let message: String
public init(code: String, message: String) {
self.code = code
self.message = message
}
}
public enum WorkoutValidation {
public static func validateSupersets(_ supersets: [[String: Any]]) -> [WorkoutValidationIssue] {
var issues = [WorkoutValidationIssue]()
if supersets.isEmpty {
issues.append(WorkoutValidationIssue(code: "empty_supersets", message: "Workout requires at least one superset."))
return issues
}
for (supersetIndex, superset) in supersets.enumerated() {
let rounds = superset["rounds"] as? Int ?? 0
if rounds <= 0 {
issues.append(WorkoutValidationIssue(code: "invalid_rounds", message: "Superset \(supersetIndex + 1) must have at least one round."))
}
let exercises = superset["exercises"] as? [[String: Any]] ?? []
if exercises.isEmpty {
issues.append(WorkoutValidationIssue(code: "empty_exercises", message: "Superset \(supersetIndex + 1) must contain at least one exercise."))
continue
}
for (exerciseIndex, exercise) in exercises.enumerated() {
let reps = exercise["reps"] as? Int ?? 0
let duration = exercise["duration"] as? Int ?? 0
if reps <= 0 && duration <= 0 {
issues.append(
WorkoutValidationIssue(
code: "invalid_exercise_payload",
message: "Exercise \(exerciseIndex + 1) in superset \(supersetIndex + 1) needs reps or duration."
)
)
}
}
}
return issues
}
}