Skip to content

Commit 32613f2

Browse files
committed
Log redacted versions of the requests if private logging is disabled
Even if we don’t want to log any sensitive information, we can still log the keys of JSON objects and insensitive values such as integers and booleans.
1 parent fd1a472 commit 32613f2

File tree

5 files changed

+166
-161
lines changed

5 files changed

+166
-161
lines changed

Sources/LanguageServerProtocolJSONRPC/LoggableMessageTypes.swift

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,6 @@ import Foundation
1414
import LanguageServerProtocol
1515
import SKLogging
1616

17-
fileprivate extension Encodable {
18-
var prettyPrintJSON: String {
19-
let encoder = JSONEncoder()
20-
encoder.outputFormatting.insert(.prettyPrinted)
21-
encoder.outputFormatting.insert(.sortedKeys)
22-
guard let data = try? encoder.encode(self) else {
23-
return "\(self)"
24-
}
25-
guard let string = String(data: data, encoding: .utf8) else {
26-
return "\(self)"
27-
}
28-
// Don't escape '/'. Most JSON readers don't need it escaped and it makes
29-
// paths a lot easier to read and copy-paste.
30-
return string.replacingOccurrences(of: "\\/", with: "/")
31-
}
32-
}
33-
3417
// MARK: - RequestType
3518

3619
fileprivate struct AnyRequestType: CustomLogStringConvertible {
@@ -39,12 +22,15 @@ fileprivate struct AnyRequestType: CustomLogStringConvertible {
3922
public var description: String {
4023
return """
4124
\(type(of: request).method)
42-
\(request.prettyPrintJSON)
25+
\(request.prettyPrintedJSON)
4326
"""
4427
}
4528

4629
public var redactedDescription: String {
47-
return "\(type(of: request).method)"
30+
return """
31+
\(type(of: request).method)
32+
\(request.prettyPrintedRedactedJSON)
33+
"""
4834
}
4935
}
5036

@@ -62,12 +48,15 @@ fileprivate struct AnyNotificationType: CustomLogStringConvertible {
6248
public var description: String {
6349
return """
6450
\(type(of: notification).method)
65-
\(notification.prettyPrintJSON)
51+
\(notification.prettyPrintedJSON)
6652
"""
6753
}
6854

6955
public var redactedDescription: String {
70-
return "\(type(of: notification).method)"
56+
return """
57+
\(type(of: notification).method)
58+
\(notification.prettyPrintedRedactedJSON)
59+
"""
7160
}
7261
}
7362

@@ -85,13 +74,14 @@ fileprivate struct AnyResponseType: CustomLogStringConvertible {
8574
var description: String {
8675
return """
8776
\(type(of: response))
88-
\(response.prettyPrintJSON)
77+
\(response.prettyPrintedJSON)
8978
"""
9079
}
9180

9281
var redactedDescription: String {
9382
return """
9483
\(type(of: response))
84+
\(response.prettyPrintedRedactedJSON)
9585
"""
9686
}
9787
}

Sources/SKLogging/CustomLogStringConvertible.swift

Lines changed: 44 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ extension String {
6464
/// A hash value that can be logged in a redacted description without
6565
/// disclosing any private information about the string.
6666
package var hashForLogging: String {
67-
return Insecure.MD5.hash(data: Data(self.utf8)).description
67+
let hash = SHA256.hash(data: Data(self.utf8)).prefix(8).map { String(format: "%02x", $0) }.joined()
68+
return "<private \(hash)>"
6869
}
6970
}
7071

@@ -86,58 +87,51 @@ extension Optional where Wrapped: CustomLogStringConvertible {
8687
}
8788
}
8889

89-
/// A JSON-like description of the object.
90-
public func recursiveDescription(of subject: Any) -> String {
91-
return "{"
92-
+ Mirror(reflecting: subject).children.map { (key, value) in
93-
"\(key ?? "<nil>"): \(String(describing: value))"
94-
}.joined(separator: ", ")
95-
+ "}"
96-
}
97-
98-
fileprivate protocol OptionalProtocol {
99-
associatedtype Wrapped
100-
var asOptional: Wrapped? { get }
101-
}
102-
extension Optional: OptionalProtocol {
103-
var asOptional: Wrapped? { self }
104-
}
105-
106-
/// A JSON-like description of the object that shows trivial values but redacts values that might contain sensitive
107-
/// information.
108-
public func recursiveRedactedDescription(of value: Any) -> String {
109-
switch value {
110-
case let value as Bool:
111-
return value.description
112-
case let value as Int:
113-
return value.description
114-
case let value as Double:
115-
return value.description
116-
case let value as String:
117-
return value.hashForLogging
118-
case let value as any OptionalProtocol:
119-
if let value = value.asOptional {
120-
return recursiveRedactedDescription(of: value)
121-
} else {
122-
return "nil"
90+
extension Encodable {
91+
package var prettyPrintedJSON: String {
92+
let encoder = JSONEncoder()
93+
encoder.outputFormatting.insert(.prettyPrinted)
94+
encoder.outputFormatting.insert(.sortedKeys)
95+
guard let data = try? encoder.encode(self) else {
96+
return "\(self)"
97+
}
98+
guard let string = String(data: data, encoding: .utf8) else {
99+
return "\(self)"
123100
}
124-
default:
125-
break
101+
// Don't escape '/'. Most JSON readers don't need it escaped and it makes
102+
// paths a lot easier to read and copy-paste.
103+
return string.replacingOccurrences(of: "\\/", with: "/")
126104
}
127105

128-
let children = Mirror(reflecting: value).children.sorted { $0.label ?? "" < $1.label ?? "" }
129-
if !children.isEmpty {
130-
let childrenKeyValuePairs = children.map { (label, value) -> String in
131-
let label = label ?? "<nil>"
132-
let value =
133-
if let value = value as? any CustomLogStringConvertible {
134-
value.redactedDescription
135-
} else {
136-
recursiveRedactedDescription(of: value)
137-
}
138-
return "\(label): \(value)"
106+
package var prettyPrintedRedactedJSON: String {
107+
func redact(subject: Any) -> Any {
108+
if let subject = subject as? [String: Any] {
109+
return subject.mapValues { redact(subject: $0) }
110+
} else if let subject = subject as? [Any] {
111+
return subject.map { redact(subject: $0) }
112+
} else if let subject = subject as? String {
113+
return subject.hashForLogging
114+
} else if let subject = subject as? Int {
115+
return subject
116+
} else if let subject = subject as? Double {
117+
return subject
118+
} else if let subject = subject as? Bool {
119+
return subject
120+
} else {
121+
return "<private>"
122+
}
123+
}
124+
125+
guard let encoded = try? JSONEncoder().encode(self),
126+
let jsonObject = try? JSONSerialization.jsonObject(with: encoded),
127+
let data = try? JSONSerialization.data(
128+
withJSONObject: redact(subject: jsonObject),
129+
options: [.prettyPrinted, .sortedKeys]
130+
),
131+
let string = String(data: data, encoding: .utf8)
132+
else {
133+
return "<private>"
139134
}
140-
return "{" + childrenKeyValuePairs.joined(separator: ", ") + "}"
135+
return string
141136
}
142-
return "<private>"
143137
}

Sources/SKOptions/SourceKitLSPOptions.swift

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import struct TSCBasic.AbsolutePath
2121
///
2222
/// See `ConfigurationFile.md` for a description of the configuration file's behavior.
2323
public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
24-
public struct SwiftPMOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
24+
public struct SwiftPMOptions: Sendable, Codable, Equatable {
2525
/// Build configuration (debug|release).
2626
///
2727
/// Equivalent to SwiftPM's `--configuration` option.
@@ -94,17 +94,9 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
9494
disableSandbox: override?.disableSandbox ?? base.disableSandbox
9595
)
9696
}
97-
98-
public var description: String {
99-
recursiveDescription(of: self)
100-
}
101-
102-
public var redactedDescription: String {
103-
recursiveRedactedDescription(of: self)
104-
}
10597
}
10698

107-
public struct CompilationDatabaseOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
99+
public struct CompilationDatabaseOptions: Sendable, Codable, Equatable {
108100
/// Additional paths to search for a compilation database, relative to a workspace root.
109101
public var searchPaths: [String]?
110102

@@ -118,17 +110,9 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
118110
) -> CompilationDatabaseOptions {
119111
return CompilationDatabaseOptions(searchPaths: override?.searchPaths ?? base.searchPaths)
120112
}
121-
122-
public var description: String {
123-
recursiveDescription(of: self)
124-
}
125-
126-
public var redactedDescription: String {
127-
recursiveRedactedDescription(of: self)
128-
}
129113
}
130114

131-
public struct FallbackBuildSystemOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
115+
public struct FallbackBuildSystemOptions: Sendable, Codable, Equatable {
132116
public var cCompilerFlags: [String]?
133117
public var cxxCompilerFlags: [String]?
134118
public var swiftCompilerFlags: [String]?
@@ -157,17 +141,9 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
157141
sdk: override?.sdk ?? base.sdk
158142
)
159143
}
160-
161-
public var description: String {
162-
recursiveDescription(of: self)
163-
}
164-
165-
public var redactedDescription: String {
166-
recursiveRedactedDescription(of: self)
167-
}
168144
}
169145

170-
public struct IndexOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
146+
public struct IndexOptions: Sendable, Codable, Equatable {
171147
public var indexStorePath: String?
172148
public var indexDatabasePath: String?
173149
public var indexPrefixMap: [String: String]?
@@ -210,17 +186,9 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
210186
updateIndexStoreTimeout: override?.updateIndexStoreTimeout ?? base.updateIndexStoreTimeout
211187
)
212188
}
213-
214-
public var description: String {
215-
recursiveDescription(of: self)
216-
}
217-
218-
public var redactedDescription: String {
219-
recursiveRedactedDescription(of: self)
220-
}
221189
}
222190

223-
public struct LoggingOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
191+
public struct LoggingOptions: Sendable, Codable, Equatable {
224192
/// The level from which one onwards log messages should be written.
225193
public var level: String?
226194
/// Whether potentially sensitive information should be redacted.
@@ -240,14 +208,6 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
240208
privacyLevel: override?.privacyLevel ?? base.privacyLevel
241209
)
242210
}
243-
244-
public var description: String {
245-
recursiveDescription(of: self)
246-
}
247-
248-
public var redactedDescription: String {
249-
recursiveRedactedDescription(of: self)
250-
}
251211
}
252212

253213
public enum BackgroundPreparationMode: String {
@@ -477,10 +437,10 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
477437
}
478438

479439
public var description: String {
480-
recursiveDescription(of: self)
440+
self.prettyPrintedJSON
481441
}
482442

483443
public var redactedDescription: String {
484-
recursiveRedactedDescription(of: self)
444+
self.prettyPrintedRedactedJSON
485445
}
486446
}

Tests/SKLoggingTests/LoggingTests.swift

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -212,46 +212,4 @@ final class LoggingTests: XCTestCase {
212212
$0.log("got \(LogStringConvertible().forLogging)")
213213
}
214214
}
215-
216-
func testRecursiveRedactedDescription() {
217-
struct Outer {
218-
struct Inner {
219-
var publicValue: Int
220-
var redactedValue: String
221-
}
222-
var inner: Inner
223-
}
224-
225-
XCTAssertEqual(
226-
recursiveRedactedDescription(of: Outer(inner: Outer.Inner(publicValue: 42, redactedValue: "password"))),
227-
"""
228-
{inner: {publicValue: 42, redactedValue: MD5 digest: 5f4dcc3b5aa765d61d8327deb882cf99}}
229-
"""
230-
)
231-
232-
XCTAssertEqual(recursiveRedactedDescription(of: (nil as Int?) as Any), "nil")
233-
234-
XCTAssertEqual(recursiveRedactedDescription(of: (42 as Int?) as Any), "42")
235-
236-
XCTAssertEqual(
237-
recursiveRedactedDescription(of: ("abc" as String?) as Any),
238-
"MD5 digest: 900150983cd24fb0d6963f7d28e17f72"
239-
)
240-
241-
struct Something: CustomLogStringConvertible {
242-
let x = 1
243-
let y = "hi"
244-
245-
var redactedDescription: String {
246-
recursiveRedactedDescription(of: self)
247-
}
248-
249-
var description: String { "" }
250-
}
251-
252-
XCTAssertEqual(
253-
Something().redactedDescription,
254-
"{x: 1, y: MD5 digest: 49f68a5c8493ec2c0bf489821c21fc3b}"
255-
)
256-
}
257215
}

0 commit comments

Comments
 (0)