Skip to content

Commit fb9dd3c

Browse files
committed
Redact secret reporter envelope context
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d6318e80-5da9-4858-a147-817e8692f10e
1 parent 7218c3e commit fb9dd3c

6 files changed

Lines changed: 306 additions & 11 deletions

File tree

libraries/reporter/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ AI fallback message text is emitted only for public envelopes. Non-public fallba
3232
and refer to the protected full-detail log. JSON oversized-record markers preserve the original privacy
3333
classification and omit non-public source and scope metadata.
3434

35+
Secret envelopes retain only protocol, event identity, ordering, timing, type, privacy, and fully redacted
36+
source and payload fields. Contextual parent, command, operation, project, phase, and scope metadata is
37+
removed.
38+
3539
## Links
3640

3741
- [CHANGELOG.md](https://github.com/microsoft/rushstack/blob/main/libraries/reporter/CHANGELOG.md) - Find out

libraries/reporter/src/qualification/AiReporterQualificationCorpus.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ const FIXED_PID: number = 4242;
2727
const CLASSIFIED_SECRET: string = 'qualification-fake-secret-token';
2828
const CLASSIFIED_SECRET_PRODUCER: string = '@secret/qualification-fixture';
2929
const CLASSIFIED_SECRET_COMPONENT: string = 'SecretQualificationFixture';
30+
const CLASSIFIED_SECRET_COMMAND: string = 'qualification-secret-command';
31+
const CLASSIFIED_SECRET_OPERATION: string = 'qualification-secret-operation';
32+
const CLASSIFIED_SECRET_PROJECT: string = '@private/qualification-secret-project';
33+
const CLASSIFIED_SECRET_PHASE: string = 'qualification-secret-phase';
34+
const CLASSIFIED_SECRET_PARENT_SESSION: string = 'qualification-secret-parent-session';
35+
const CLASSIFIED_SECRET_PARENT_OPERATION: string = 'qualification-secret-parent-operation';
36+
const CLASSIFIED_SECRET_MESSAGE: string = 'qualification-secret-message-text';
37+
const CLASSIFIED_SECRET_DIAGNOSTIC: string = 'qualification-secret-diagnostic-summary';
3038
const PRIVATE_PRODUCER: string = '@private/example-rush-plugin';
3139
const PRIVATE_COMPONENT: string = 'PrivatePluginImplementation';
3240
const LOCAL_SENSITIVE_FALLBACK_MESSAGE: string = 'qualification-local-sensitive-fallback-message';
@@ -637,6 +645,74 @@ function createEvents(testCase: ICorpusCase, logPath: string): IReporterEventEnv
637645
return events;
638646
}
639647

648+
function createSecretProjectionProbeEvents(testCase: ICorpusCase): IReporterEventEnvelope<unknown>[] {
649+
const createProbe = (
650+
eventId: string,
651+
sequence: number,
652+
type: IReporterEventEnvelope<unknown>['type'],
653+
payload: unknown,
654+
scope: IReporterEventEnvelope<unknown>['scope'] = {
655+
commandName: CLASSIFIED_SECRET_COMMAND,
656+
operationId: CLASSIFIED_SECRET_OPERATION,
657+
projectName: CLASSIFIED_SECRET_PROJECT,
658+
phaseName: CLASSIFIED_SECRET_PHASE
659+
}
660+
): IReporterEventEnvelope<unknown> => ({
661+
protocolVersion: { major: 1, minor: 1 },
662+
eventId,
663+
sessionId: `${testCase.name}-session`,
664+
parentSessionId: CLASSIFIED_SECRET_PARENT_SESSION,
665+
parentOperationId: CLASSIFIED_SECRET_PARENT_OPERATION,
666+
sequence,
667+
sourceSequence: sequence - 10000,
668+
timestamp: FIXED_TIMESTAMP,
669+
source: {
670+
packageName: CLASSIFIED_SECRET_PRODUCER,
671+
packageVersion: '1.0.0',
672+
component: CLASSIFIED_SECRET_COMPONENT
673+
},
674+
scope,
675+
privacy: 'secret',
676+
required: true,
677+
type,
678+
payload
679+
});
680+
681+
return [
682+
createProbe(
683+
`${testCase.name}-secret-command`,
684+
10001,
685+
'commandStarted',
686+
{
687+
commandName: CLASSIFIED_SECRET_COMMAND
688+
},
689+
{
690+
commandName: CLASSIFIED_SECRET_COMMAND
691+
}
692+
),
693+
createProbe(`${testCase.name}-secret-operation-registered`, 10002, 'operationRegistered', {
694+
operationId: CLASSIFIED_SECRET_OPERATION,
695+
projectName: CLASSIFIED_SECRET_PROJECT,
696+
phaseName: CLASSIFIED_SECRET_PHASE
697+
}),
698+
createProbe(`${testCase.name}-secret-operation-completed`, 10003, 'operationCompleted', {
699+
operationId: CLASSIFIED_SECRET_OPERATION,
700+
status: 'failure'
701+
}),
702+
createProbe(`${testCase.name}-secret-message`, 10004, 'messageEmitted', {
703+
severity: 'info',
704+
text: CLASSIFIED_SECRET_MESSAGE
705+
}),
706+
createProbe(`${testCase.name}-secret-diagnostic`, 10005, 'diagnosticEmitted', {
707+
diagnosticId: `${testCase.name}-secret-diagnostic`,
708+
code: 'RUSH_INTERNAL_UNEXPECTED',
709+
category: 'internal',
710+
severity: 'info',
711+
summary: CLASSIFIED_SECRET_DIAGNOSTIC
712+
})
713+
];
714+
}
715+
640716
async function runCaseAsync(
641717
testCase: ICorpusCase,
642718
caseDirectory: string,
@@ -678,6 +754,15 @@ async function runCaseAsync(
678754
plaintextReporter.report(event);
679755
legacyReporter.report(event);
680756
}
757+
for (const event of createSecretProjectionProbeEvents(testCase)) {
758+
// Operation grouping is a separate file-sidecar policy. Exercise shared
759+
// file redaction with records that do not enter the grouping path.
760+
if (event.type === 'messageEmitted' || event.type === 'diagnosticEmitted') {
761+
fileReporter.report(event);
762+
}
763+
aiReporter.report(event);
764+
jsonReporter.report(event);
765+
}
681766
await fileReporter.closeAsync();
682767
await aiReporter.closeAsync();
683768
await jsonReporter.closeAsync();
@@ -820,6 +905,14 @@ async function runCaseAsync(
820905
!allLocalOutput.includes(CLASSIFIED_SECRET) &&
821906
!allLocalOutput.includes(CLASSIFIED_SECRET_PRODUCER) &&
822907
!allLocalOutput.includes(CLASSIFIED_SECRET_COMPONENT) &&
908+
!allLocalOutput.includes(CLASSIFIED_SECRET_COMMAND) &&
909+
!allLocalOutput.includes(CLASSIFIED_SECRET_OPERATION) &&
910+
!allLocalOutput.includes(CLASSIFIED_SECRET_PROJECT) &&
911+
!allLocalOutput.includes(CLASSIFIED_SECRET_PHASE) &&
912+
!allLocalOutput.includes(CLASSIFIED_SECRET_PARENT_SESSION) &&
913+
!allLocalOutput.includes(CLASSIFIED_SECRET_PARENT_OPERATION) &&
914+
!allLocalOutput.includes(CLASSIFIED_SECRET_MESSAGE) &&
915+
!allLocalOutput.includes(CLASSIFIED_SECRET_DIAGNOSTIC) &&
823916
!machinePresentedOutput.includes(LOCAL_SENSITIVE_FALLBACK_MESSAGE) &&
824917
!machinePresentedOutput.includes(OVERSIZED_LOCAL_SENSITIVE_VALUE) &&
825918
!machinePresentedOutput.includes(OVERSIZED_LOCAL_SENSITIVE_PRODUCER) &&

libraries/reporter/src/reporters/AiReporter.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,17 @@ export class AiReporter implements IReporter {
205205

206206
public report(event: IReporterEventEnvelope<unknown>): void {
207207
this._protocolVersion = event.protocolVersion;
208+
if (event.privacy === 'secret') {
209+
switch (event.type) {
210+
case 'diagnosticEmitted':
211+
case 'messageEmitted':
212+
case 'commandResult':
213+
case 'sessionCompleted':
214+
break;
215+
default:
216+
return;
217+
}
218+
}
208219
switch (event.type) {
209220
case 'commandStarted': {
210221
this._commandName = (event.payload as { commandName: string }).commandName;

libraries/reporter/src/reporters/ReporterRedaction.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,27 @@ export function getHumanReadableMessageText(event: IReporterEventEnvelope<unknow
1717
}
1818

1919
export function redactReporterEvent(event: IReporterEventEnvelope<unknown>): IReporterEventEnvelope<unknown> {
20-
let payload: unknown = event.payload;
21-
const source: IReporterEventEnvelope<unknown>['source'] =
22-
event.privacy === 'secret'
23-
? {
24-
packageName: '[private-producer]',
25-
packageVersion: '[private-version]'
26-
}
27-
: event.source;
2820
if (event.privacy === 'secret') {
29-
payload = '[secret]';
30-
} else if (event.type === 'diagnosticEmitted') {
21+
return {
22+
protocolVersion: event.protocolVersion,
23+
eventId: event.eventId,
24+
sessionId: event.sessionId,
25+
sequence: event.sequence,
26+
sourceSequence: event.sourceSequence,
27+
timestamp: event.timestamp,
28+
source: {
29+
packageName: '[private-producer]',
30+
packageVersion: '[private-version]'
31+
},
32+
privacy: 'secret',
33+
required: event.required,
34+
type: event.type,
35+
payload: '[secret]'
36+
};
37+
}
38+
39+
let payload: unknown = event.payload;
40+
if (event.type === 'diagnosticEmitted') {
3141
const diagnostic: {
3242
readonly parameters?: Readonly<Record<string, IClassifiedValue>>;
3343
readonly source?: unknown;
@@ -49,5 +59,5 @@ export function redactReporterEvent(event: IReporterEventEnvelope<unknown>): IRe
4959
}
5060
payload = redactedDiagnostic;
5161
}
52-
return { ...event, source, payload };
62+
return { ...event, payload };
5363
}

libraries/reporter/src/test/AiReporterQualification.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ describe('AI reporter deterministic qualification corpus', () => {
3232
const serialized: string = JSON.stringify(qualification);
3333
expect(serialized).not.toContain('rush-ai-reporter-qualification-');
3434
expect(serialized).not.toContain('qualification-fake-secret-token');
35+
expect(serialized).not.toContain('qualification-secret-command');
36+
expect(serialized).not.toContain('qualification-secret-operation');
37+
expect(serialized).not.toContain('@private/qualification-secret-project');
38+
expect(serialized).not.toContain('qualification-secret-phase');
39+
expect(serialized).not.toContain('qualification-secret-parent-session');
40+
expect(serialized).not.toContain('qualification-secret-parent-operation');
41+
expect(serialized).not.toContain('qualification-secret-message-text');
42+
expect(serialized).not.toContain('qualification-secret-diagnostic-summary');
3543
expect(serialized).not.toContain('qualification-local-sensitive-fallback-message');
3644
expect(serialized).not.toContain('qualification-oversized-local-sensitive-value');
3745
expect(serialized).not.toContain('@private/oversized-qualification-fixture');

0 commit comments

Comments
 (0)