Skip to content

chore(core/schema): use schema log filter when available #1662

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

Merged
merged 1 commit into from
Aug 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lemon-wasps-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smithy/smithy-client": minor
---

default schema log filter
14 changes: 10 additions & 4 deletions packages/smithy-client/src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import type {
} from "@smithy/types";
import { SMITHY_CONTEXT_KEY } from "@smithy/types";

import { schemaLogFilter } from "./schemaLogFilter";

/**
* @public
*/
Expand Down Expand Up @@ -130,8 +132,8 @@ class ClassBuilder<
private _clientName = "";
private _additionalContext = {} as HandlerExecutionContext;
private _smithyContext = {} as Record<string, unknown>;
private _inputFilterSensitiveLog = (_: any) => _;
private _outputFilterSensitiveLog = (_: any) => _;
private _inputFilterSensitiveLog: any = undefined;
private _outputFilterSensitiveLog: any = undefined;
private _serializer: (input: I, context: SerdeContext | any) => Promise<IHttpRequest> = null as any;
private _deserializer: (output: IHttpResponse, context: SerdeContext | any) => Promise<O> = null as any;
private _operationSchema?: OperationSchema;
Expand Down Expand Up @@ -268,8 +270,12 @@ class ClassBuilder<
middlewareFn: closure._middlewareFn,
clientName: closure._clientName,
commandName: closure._commandName,
inputFilterSensitiveLog: closure._inputFilterSensitiveLog,
outputFilterSensitiveLog: closure._outputFilterSensitiveLog,
inputFilterSensitiveLog:
closure._inputFilterSensitiveLog ??
(closure._operationSchema ? schemaLogFilter.bind(null, closure._operationSchema!.input) : (_) => _),
outputFilterSensitiveLog:
closure._outputFilterSensitiveLog ??
(closure._operationSchema ? schemaLogFilter.bind(null, closure._operationSchema!.output) : (_) => _),
smithyContext: closure._smithyContext,
additionalContext: closure._additionalContext,
});
Expand Down
88 changes: 88 additions & 0 deletions packages/smithy-client/src/schemaLogFilter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { list, map, SCHEMA, sim, struct } from "@smithy/core/schema";
import { describe, expect, test as it } from "vitest";

import { schemaLogFilter } from "./schemaLogFilter";

describe(schemaLogFilter.name, () => {
it("should filter sensitive trait-marked fields", () => {
const sensitiveString = sim("ns", "SensitiveString", 0, { sensitive: 1 });

const schema = struct(
"ns",
"Struct",
0,
["a", "b", "sensitive", "nestedSensitive", "various"],
[
SCHEMA.STRING,
SCHEMA.STRING,
sensitiveString,
struct("ns", "NestedSensitiveStruct", 0, ["sensitive"], [sensitiveString]),
struct(
"ns",
"Various",
0,
["boolean", "number", "struct", "list-s", "list", "map-s", "map"],
[
sim("ns", "Boolean", SCHEMA.BOOLEAN, { sensitive: 1 }),
sim("ns", "Numeric", SCHEMA.NUMERIC, { sensitive: 1 }),
struct("ns", "SensitiveStruct", { sensitive: 1 }, [], []),
list("ns", "List", 0, sensitiveString),
list("ns", "List", 0, SCHEMA.STRING),
map("ns", "Map", 0, sensitiveString, SCHEMA.STRING),
map("ns", "Map", 0, SCHEMA.STRING, SCHEMA.STRING),
]
),
]
);

expect(
schemaLogFilter(schema, {
a: "a",
b: "b",
sensitive: "xyz",
nestedSensitive: {
sensitive: "xyz",
},
various: {
boolean: false,
number: 1,
struct: {
q: "rf",
},
"list-s": [1, 2, 3],
list: [4, 5, 6],
"map-s": {
a: "a",
b: "b",
c: "c",
},
map: {
a: "d",
b: "e",
c: "f",
},
},
})
).toEqual({
a: "a",
b: "b",
sensitive: "***SensitiveInformation***",
nestedSensitive: {
sensitive: "***SensitiveInformation***",
},
various: {
boolean: "***SensitiveInformation***",
number: "***SensitiveInformation***",
struct: "***SensitiveInformation***",
"list-s": "***SensitiveInformation***",
list: [4, 5, 6],
"map-s": "***SensitiveInformation***",
map: {
a: "d",
b: "e",
c: "f",
},
},
});
});
});
46 changes: 46 additions & 0 deletions packages/smithy-client/src/schemaLogFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { NormalizedSchema } from "@smithy/core/schema";
import type { SchemaRef } from "@smithy/types";

const SENSITIVE_STRING = "***SensitiveInformation***";

/**
* Redacts sensitive parts of any data object using its schema, for logging.
*
* @internal
* @param schema - with filtering traits.
* @param data - to be logged.
*/
export function schemaLogFilter(schema: SchemaRef, data: unknown): any {
if (data == null) {
return data;
}
const ns = NormalizedSchema.of(schema);
if (ns.getMergedTraits().sensitive) {
return SENSITIVE_STRING;
}

if (ns.isListSchema()) {
const isSensitive = !!ns.getValueSchema().getMergedTraits().sensitive;
if (isSensitive) {
return SENSITIVE_STRING;
}
} else if (ns.isMapSchema()) {
const isSensitive =
!!ns.getKeySchema().getMergedTraits().sensitive || !!ns.getValueSchema().getMergedTraits().sensitive;
if (isSensitive) {
return SENSITIVE_STRING;
}
} else if (ns.isStructSchema() && typeof data === "object") {
const object = data as Record<string, unknown>;

const newObject = {} as any;
for (const [member, memberNs] of ns.structIterator()) {
if (object[member] != null) {
newObject[member] = schemaLogFilter(memberNs, object[member]);
}
}
return newObject;
}

return data;
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export class EmptyInputOutputCommand extends $Command
})
.s("RpcV2Protocol", "EmptyInputOutput", {})
.n("RpcV2ProtocolClient", "EmptyInputOutputCommand")
.f(void 0, void 0)
.sc(EmptyInputOutput)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ export class Float16Command extends $Command
})
.s("RpcV2Protocol", "Float16", {})
.n("RpcV2ProtocolClient", "Float16Command")
.f(void 0, void 0)
.sc(Float16)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ export class FractionalSecondsCommand extends $Command
})
.s("RpcV2Protocol", "FractionalSeconds", {})
.n("RpcV2ProtocolClient", "FractionalSecondsCommand")
.f(void 0, void 0)
.sc(FractionalSeconds)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ export class GreetingWithErrorsCommand extends $Command
})
.s("RpcV2Protocol", "GreetingWithErrors", {})
.n("RpcV2ProtocolClient", "GreetingWithErrorsCommand")
.f(void 0, void 0)
.sc(GreetingWithErrors)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export class NoInputOutputCommand extends $Command
})
.s("RpcV2Protocol", "NoInputOutput", {})
.n("RpcV2ProtocolClient", "NoInputOutputCommand")
.f(void 0, void 0)
.sc(NoInputOutput)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ export class OperationWithDefaultsCommand extends $Command
})
.s("RpcV2Protocol", "OperationWithDefaults", {})
.n("RpcV2ProtocolClient", "OperationWithDefaultsCommand")
.f(void 0, void 0)
.sc(OperationWithDefaults)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export class OptionalInputOutputCommand extends $Command
})
.s("RpcV2Protocol", "OptionalInputOutput", {})
.n("RpcV2ProtocolClient", "OptionalInputOutputCommand")
.f(void 0, void 0)
.sc(OptionalInputOutput)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ export class RecursiveShapesCommand extends $Command
})
.s("RpcV2Protocol", "RecursiveShapes", {})
.n("RpcV2ProtocolClient", "RecursiveShapesCommand")
.f(void 0, void 0)
.sc(RecursiveShapes)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ export class RpcV2CborDenseMapsCommand extends $Command
})
.s("RpcV2Protocol", "RpcV2CborDenseMaps", {})
.n("RpcV2ProtocolClient", "RpcV2CborDenseMapsCommand")
.f(void 0, void 0)
.sc(RpcV2CborDenseMaps)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ export class RpcV2CborListsCommand extends $Command
})
.s("RpcV2Protocol", "RpcV2CborLists", {})
.n("RpcV2ProtocolClient", "RpcV2CborListsCommand")
.f(void 0, void 0)
.sc(RpcV2CborLists)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ export class RpcV2CborSparseMapsCommand extends $Command
})
.s("RpcV2Protocol", "RpcV2CborSparseMaps", {})
.n("RpcV2ProtocolClient", "RpcV2CborSparseMapsCommand")
.f(void 0, void 0)
.sc(RpcV2CborSparseMaps)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ export class SimpleScalarPropertiesCommand extends $Command
})
.s("RpcV2Protocol", "SimpleScalarProperties", {})
.n("RpcV2ProtocolClient", "SimpleScalarPropertiesCommand")
.f(void 0, void 0)
.sc(SimpleScalarProperties)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export class SparseNullsOperationCommand extends $Command
})
.s("RpcV2Protocol", "SparseNullsOperation", {})
.n("RpcV2ProtocolClient", "SparseNullsOperationCommand")
.f(void 0, void 0)
.sc(SparseNullsOperation)
.build() {
/** @internal type navigation helper, not in runtime. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,9 +453,10 @@ private void generateEndpointParameterInstructionProvider() {

private void generateCommandMiddlewareResolver(String configType) {
Symbol serde = TypeScriptDependency.MIDDLEWARE_SERDE.createSymbol("getSerdePlugin");
boolean schemaMode = SchemaGenerationAllowlist.allows(service.getId(), settings);

Function<StructureShape, String> getFilterFunctionName = input -> {
if (sensitiveDataFinder.findsSensitiveDataIn(input)) {
if (sensitiveDataFinder.findsSensitiveDataIn(input) && !schemaMode) {
Symbol inputSymbol = symbolProvider.toSymbol(input);
String filterFunctionName = inputSymbol.getName() + "FilterSensitiveLog";
writer.addRelativeImport(
Expand Down Expand Up @@ -496,7 +497,7 @@ private void generateCommandMiddlewareResolver(String configType) {
);
{
// Add serialization and deserialization plugin.
if (!SchemaGenerationAllowlist.allows(service.getId(), settings)) {
if (!schemaMode) {
writer.write("$T(config, this.serialize, this.deserialize),", serde);
}

Expand All @@ -518,15 +519,16 @@ private void generateCommandMiddlewareResolver(String configType) {
})"""
); // end middleware.

String filters = schemaMode ? "" : ".f($inputFilter:L, $outputFilter:L)";

// context, filters
writer.openBlock(
"""
.s($service:S, $operation:S, {
""",
"""
})
.n($client:S, $command:S)
.f($inputFilter:L, $outputFilter:L)""",
.n($client:S, $command:S)%s""".formatted(filters),
() -> {
writer.pushState(SmithyContextCodeSection.builder()
.settings(settings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,12 +364,16 @@ private void generateServiceInterface(GenerateServiceDirective<TypeScriptCodegen
public void generateStructure(GenerateStructureDirective<TypeScriptCodegenContext, TypeScriptSettings> directive) {
directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> {
StructureGenerator generator = new StructureGenerator(
directive.model(),
directive.symbolProvider(),
writer,
directive.shape(),
directive.settings().generateServerSdk(),
directive.settings().getRequiredMemberMode()
directive.model(),
directive.symbolProvider(),
writer,
directive.shape(),
directive.settings().generateServerSdk(),
directive.settings().getRequiredMemberMode(),
SchemaGenerationAllowlist.allows(
directive.settings().getService(),
directive.settings()
)
);
generator.run();
});
Expand All @@ -379,12 +383,16 @@ public void generateStructure(GenerateStructureDirective<TypeScriptCodegenContex
public void generateError(GenerateErrorDirective<TypeScriptCodegenContext, TypeScriptSettings> directive) {
directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> {
StructureGenerator generator = new StructureGenerator(
directive.model(),
directive.symbolProvider(),
writer,
directive.shape(),
directive.settings().generateServerSdk(),
directive.settings().getRequiredMemberMode()
directive.model(),
directive.symbolProvider(),
writer,
directive.shape(),
directive.settings().generateServerSdk(),
directive.settings().getRequiredMemberMode(),
SchemaGenerationAllowlist.allows(
directive.settings().getService(),
directive.settings()
)
);
generator.run();
});
Expand All @@ -394,11 +402,15 @@ public void generateError(GenerateErrorDirective<TypeScriptCodegenContext, TypeS
public void generateUnion(GenerateUnionDirective<TypeScriptCodegenContext, TypeScriptSettings> directive) {
directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> {
UnionGenerator generator = new UnionGenerator(
directive.model(),
directive.symbolProvider(),
writer,
directive.shape(),
directive.settings().generateServerSdk()
directive.model(),
directive.symbolProvider(),
writer,
directive.shape(),
directive.settings().generateServerSdk(),
SchemaGenerationAllowlist.allows(
directive.settings().getService(),
directive.settings()
)
);
generator.run();
});
Expand Down
Loading
Loading