Skip to content

Commit 78667d5

Browse files
author
Vieltojarvi
committed
fix: remove user data from telemetry
1 parent ef4bf37 commit 78667d5

File tree

6 files changed

+130
-6
lines changed

6 files changed

+130
-6
lines changed

packages/amplify-cli/src/__tests__/flow-report.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import { v4 as uuid } from 'uuid';
1616
import { Redactor } from '@aws-amplify/amplify-cli-logger';
1717
import { CLIFlowReport } from '../domain/amplify-usageData/FlowReport';
18+
import { homedir } from 'os';
1819

1920
describe('Test FlowReport Logging', () => {
2021
beforeAll(() => {});
@@ -103,6 +104,20 @@ describe('Test FlowReport Logging', () => {
103104
expect(flowReport.optionFlowData[0].input).toContain(input.serviceConfiguration.mapStyle);
104105
expect(flowReport.optionFlowData[0].input).toEqual(Redactor(inputString));
105106
});
107+
108+
it('runtime does not contain home directory info', () => {
109+
const flowReport = new CLIFlowReport();
110+
flowReport.setInput(mockInputs.headlessInput('auth', 'add'));
111+
112+
expect(flowReport.runtime).not.toContain(homedir());
113+
});
114+
115+
it('executable does not contain home directory info', () => {
116+
const flowReport = new CLIFlowReport();
117+
flowReport.setInput(mockInputs.headlessInput('auth', 'add'));
118+
119+
expect(flowReport.executable).not.toContain(homedir());
120+
});
106121
});
107122

108123
const redactValue = (key: string, value: unknown) => {
@@ -180,7 +195,7 @@ const mockInputs = {
180195
Geo: mockAddGeoInput,
181196
GraphQLAPI: mockAddAPIInput,
182197
headlessInput: (feature, command) => ({
183-
argv: [],
198+
argv: [`${homedir()}/.amplify/bin/amplify.exe`, `${homedir()}/amplify-cli/build/node_modules/@aws-amplify/cli-internal/bin/amplify`],
184199
plugin: feature,
185200
command,
186201
}),

packages/amplify-cli/src/__tests__/serializable-error.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,23 @@ describe('test serializabe error', () => {
5454
expect(serializableError.name).toBe('NotImplementedError');
5555
expect(serializableError.details).toBe('some error details with arn: <escaped ARN> and arn: <escaped ARN> and something else');
5656
});
57+
58+
it('test SerializableError has no stack arns in error message', () => {
59+
const testStack = 'amplify-someStackName-dev-u9910';
60+
const error = new AmplifyError('NotImplementedError', {
61+
message: `Could not initialize ${testStack}`,
62+
});
63+
64+
const serializableError = new SerializableError(error);
65+
expect(serializableError.message).toBe('Could not initialize <escaped stack>');
66+
});
67+
68+
it('test SerializeError error message has no home directories in file paths', () => {
69+
const error = new AmplifyError('FileSystemPermissionsError', {
70+
message: `Permission denied, open '${os.homedir}/.amplify/logs/amplify-cli.log`,
71+
});
72+
73+
const serializableError = new SerializableError(error);
74+
expect(serializableError.message).not.toContain(os.homedir());
75+
});
5776
});

packages/amplify-cli/src/__tests__/usage-data.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,41 @@ describe('test usageData', () => {
6767
expect(a).toEqual(b);
6868
});
6969

70+
it('expects accountId to be a valid UUID', () => {
71+
const a = UsageData.Instance;
72+
const timeStamp = Date.now();
73+
const testAccountID = '123456789012';
74+
a.init(
75+
uuid.v4(),
76+
'',
77+
new CommandLineInput([]),
78+
testAccountID,
79+
{ editor: 'vscode', framework: 'react', frontend: 'javascript' } as unknown as ProjectSettings,
80+
timeStamp,
81+
);
82+
83+
expect(uuid.validate(a.getUsageDataPayload(null, 'SUCCESS').accountId)).toEqual(true);
84+
});
85+
86+
it('returns cached accountId on subsequent calls', () => {
87+
const a = UsageData.Instance;
88+
const timeStamp = Date.now();
89+
const testAccountID = '123456789012';
90+
a.init(
91+
uuid.v4(),
92+
'',
93+
new CommandLineInput([]),
94+
testAccountID,
95+
{ editor: 'vscode', framework: 'react', frontend: 'javascript' } as unknown as ProjectSettings,
96+
timeStamp,
97+
);
98+
99+
const accountId1 = a.getUsageDataPayload(null, 'SUCCESS').accountId;
100+
const accountId2 = a.getUsageDataPayload(null, 'SUCCESS').accountId;
101+
102+
expect(accountId1).toEqual(accountId2);
103+
});
104+
70105
it('records specified code path timer', async () => {
71106
const usageData = UsageData.Instance;
72107
usageData.startCodePathTimer(ManuallyTimedCodePath.PLUGIN_TIME);

packages/amplify-cli/src/domain/amplify-usageData/FlowReport.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
TypeOptionFlowData,
1313
} from '@aws-amplify/amplify-cli-shared-interfaces';
1414
import { createHashedIdentifier } from '../../commands/helpers/encryption-helpers';
15+
import { homedir } from 'os';
1516

1617
/**
1718
* Store the data and sequence of events of CLI walkthrough
@@ -32,6 +33,11 @@ export class CLIFlowReport implements IFlowData {
3233
projectIdentifier?: string; // hash( ProjectName + Amplify App Id)
3334
envName?: string;
3435

36+
// (file:/+)? -> matches optional file url prefix
37+
// homedir() -> users home directory, replacing \ with /
38+
// [\\w.\\-_@\\\\/]+ -> matches nested directories and file name
39+
filePathRegex = new RegExp(`(file:/+)?${homedir().replaceAll('\\', '/')}[\\w.\\-_@\\\\/]+`, 'g');
40+
3541
constructor() {
3642
const currentTime = Date.now();
3743
this.logger = getAmplifyLogger();
@@ -66,8 +72,15 @@ export class CLIFlowReport implements IFlowData {
6672
*/
6773
setInput(input: CLIInput): void {
6874
this.input = input;
69-
this.runtime = input.argv[0] as string;
70-
this.executable = input.argv[1] as string;
75+
for (let i = 0; i < input.argv.length; i++) {
76+
if (input.argv[i].match(this.filePathRegex)) {
77+
const arg = this.anonymizePath(input.argv[i]);
78+
this.input.argv[i] = arg;
79+
}
80+
}
81+
82+
this.runtime = this.input.argv[0] as string;
83+
this.executable = this.input.argv[1] as string;
7184
this.cmd = input.argv[2] as string;
7285
this.subCmd = input.argv[3] ? input.argv[3] : undefined;
7386
this.optionFlowData = []; // key-value store with ordering maintained
@@ -139,4 +152,12 @@ export class CLIFlowReport implements IFlowData {
139152
const timeStampedOption: IOptionFlowHeadlessData = { input: cleanOption, timestamp: new Date().valueOf() }; // attach unix-style timestamp
140153
this.optionFlowData.push(timeStampedOption);
141154
}
155+
156+
private anonymizePath = (str: string): string => {
157+
let result = str;
158+
if (result.match(this.filePathRegex)) {
159+
result = result.replace(this.filePathRegex, '');
160+
}
161+
return result;
162+
};
142163
}

packages/amplify-cli/src/domain/amplify-usageData/SerializableError.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import { $TSAny } from '@aws-amplify/amplify-cli-core';
2+
import { homedir } from 'os';
23
import * as path from 'path';
34

45
const stackTraceRegex = /^\s*at (?:((?:\[object object\])?[^\\/]+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i;
56
const ARNRegex =
67
/arn:[a-z0-9][-.a-z0-9]{0,62}:[A-Za-z0-9][A-Za-z0-9_/.-]{0,62}:[A-Za-z0-9_/.-]{0,63}:[A-Za-z0-9_/.-]{0,63}:[A-Za-z0-9][A-Za-z0-9:_/+=,@.-]{0,1023}/g;
78

9+
// Gen1 stacks have same structure as Gen2 stacks
10+
const stackRegex = /amplify-[a-zA-Z0-9-]+/g;
11+
// (file:/+)? -> matches optional file url prefix
12+
// homedir() -> users home directory, replacing \ with /
13+
// [\\w.\\-_@\\\\/]+ -> matches nested directories and file name
14+
const filePathRegex = new RegExp(`(file:/+)?${homedir().replaceAll('\\', '/')}[\\w.\\-_@\\\\/]+`, 'g');
15+
816
/**
917
* Wrapper around Error.name
1018
*/
@@ -16,7 +24,7 @@ export class SerializableError {
1624
trace?: Trace[];
1725
constructor(error: Error) {
1826
this.name = error.name;
19-
this.message = removeARN(error.message)!;
27+
this.message = anonymizePaths(sanitize(error.message)!);
2028
this.details = removeARN((error as $TSAny)?.details);
2129
this.code = (error as $TSAny)?.code;
2230
this.trace = extractStackTrace(error);
@@ -83,10 +91,33 @@ const processPaths = (paths: string[]): string[] => {
8391
});
8492
};
8593

94+
const sanitize = (str?: string): string | undefined => {
95+
let result = str;
96+
result = removeARN(result);
97+
result = removeStackIdentifier(result);
98+
99+
return result;
100+
};
101+
86102
const removeARN = (str?: string): string | undefined => {
87103
return str?.replace(ARNRegex, '<escaped ARN>');
88104
};
89105

106+
const removeStackIdentifier = (str?: string): string | undefined => {
107+
return str?.replace(stackRegex, '<escaped stack>') ?? '';
108+
};
109+
110+
const anonymizePaths = (str: string): string => {
111+
const result = str;
112+
const matches = [...result.matchAll(filePathRegex)];
113+
114+
for (const match of matches) {
115+
result.replace(match[0], processPaths([match[0]])[0]);
116+
}
117+
118+
return result;
119+
};
120+
90121
type Trace = {
91122
methodName: string;
92123
file: string;

packages/amplify-cli/src/domain/amplify-usageData/UsageData.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { prompter, printer } from '@aws-amplify/amplify-prompts';
44
import https from 'https';
55
import { pick } from 'lodash';
66
import { UrlWithStringQuery } from 'url';
7-
import { v4 as uuid } from 'uuid';
7+
import { v4 as uuid, v5 as uuidV5 } from 'uuid';
88
import { CLIInput } from '../command-input';
99
import {
1010
JSONUtilities,
@@ -24,6 +24,8 @@ import redactInput from './identifiable-input-regex';
2424
import { Timer } from './Timer';
2525
import { UsageDataPayload } from './UsageDataPayload';
2626

27+
const AMPLIFY_CLI_UUID_NAMESPACE = '7bf41e64-b0be-4a57-8d92-9cfbc1600f0f';
28+
2729
/**
2830
* Singleton class that manages the lifecycle of usage data during a CLI command
2931
*/
@@ -63,7 +65,8 @@ export class UsageData implements IUsageData {
6365
processStartTimeStamp: number,
6466
): void {
6567
this.installationUuid = installationUuid;
66-
this.accountId = accountId;
68+
const accountBucketId = Number(accountId) / 100;
69+
this.accountId = uuidV5(accountBucketId.toString(), AMPLIFY_CLI_UUID_NAMESPACE);
6770
this.projectSettings = projectSettings;
6871
this.version = version;
6972
this.inputOptions = input.options ? pick(input.options as InputOptions, ['sandboxId']) : {};

0 commit comments

Comments
 (0)