Skip to content
Open
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
22 changes: 22 additions & 0 deletions apps/desktop/src/main/__tests__/rive-workflow-tool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { join } from 'node:path';
import { describe, it } from 'node:test';
import {
buildRiveCommand,
redactRiveText,
redactRiveValue,
runRiveCli,
RiveCliError,
} from '../rive-cli.js';
Expand Down Expand Up @@ -243,6 +245,26 @@ describe('RiveWorkflow tool and CLI bridge', { concurrency: false }, () => {
});
});

it('uses the core redaction coverage for token forms and sensitive keys', () => {
const text = redactRiveText(
'ghp_12345678901234567890 AIza12345678901234567890 xoxb-1234567890 Bearer opaque-session-token',
);
assert.equal(text.includes('ghp_12345678901234567890'), false);
assert.equal(text.includes('AIza12345678901234567890'), false);
assert.equal(text.includes('xoxb-1234567890'), false);
assert.equal(text.includes('opaque-session-token'), false);
assert.match(text, /Bearer \[redacted\]/);

const value = redactRiveValue({
apiKey: 'plain-value',
nested: [{ authorization: 'Bearer plain-value' }],
});
assert.deepEqual(value, {
apiKey: '[redacted]',
nested: [{ authorization: '[redacted]' }],
});
});

});

async function runTool(
Expand Down
9 changes: 3 additions & 6 deletions apps/desktop/src/main/rive-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import { access } from 'node:fs/promises';
import { constants } from 'node:fs';
import { spawn } from 'node:child_process';
import { isSensitiveKey, redactSecrets } from '@maka/core/redaction';

export type RiveCliAction =
| 'workflow_validate'
Expand Down Expand Up @@ -177,11 +178,7 @@ export async function runRiveCli(input: RiveCliToolArgs, options: RiveCliRunOpti
}

export function redactRiveText(input: string): string {
return input
.replace(/\b(Bearer\s+)[A-Za-z0-9._~+/-]+=*/gi, '$1[REDACTED]')
.replace(/\b(sk-[A-Za-z0-9][A-Za-z0-9_-]{8,})\b/g, '[REDACTED]')
.replace(/\b((?:api[_-]?key|token|secret|password)\s*[:=]\s*)("[^"]+"|'[^']+'|[^\s,;]+)/gi, '$1[REDACTED]')
.replace(/\b([A-Za-z0-9_-]*(?:token|secret|password|api[_-]?key)[A-Za-z0-9_-]*\s*[:=]\s*)("[^"]+"|'[^']+'|[^\s,;]+)/gi, '$1[REDACTED]');
return redactSecrets(input);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Preserve masking of quoted secrets containing spaces

Delegating to the shared redactor loses a form covered by the removed Rive regex. Exact before/after execution for password="correct horse battery staple" gives password=[REDACTED] before, but password="[redacted] horse battery staple" now. Likewise client_secret: 'two secret words' now leaves secret words visible. The core assignment regex stops at whitespace even inside quotes; these inputs are ordinary CLI diagnostic text, not JSON, so the structured-JSON path does not help. Rive forwards this result through emitOutput for stdout/stderr and stores it in error tails (rive-cli.ts:270-297). Extend the shared authority to consume the whole quoted value and keep a regression at the Rive boundary before removing the old coverage.

}

export function redactRiveValue(value: unknown, depth = 0): unknown {
Expand All @@ -191,7 +188,7 @@ export function redactRiveValue(value: unknown, depth = 0): unknown {
if (!value || typeof value !== 'object') return value;
const out: Record<string, unknown> = {};
for (const [key, item] of Object.entries(value)) {
out[key] = redactRiveValue(item, depth + 1);
out[key] = isSensitiveKey(key) ? '[redacted]' : redactRiveValue(item, depth + 1);
}
return out;
}
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/__tests__/redaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ describe('redactSecrets', () => {
assert.match(inspected, /proxyAuthorization: 'Token \[redacted\]'/);
});

test('masks standalone bearer values', () => {
const text = redactSecrets('prefix Bearer opaque-session-token suffix');

assert.equal(text, 'prefix Bearer [redacted] suffix');
assert.equal(text.includes('opaque-session-token'), false);
});

test('applies bounded text patterns to top-level JSON number primitives', () => {
assert.equal(redactSecrets('1234567890123456789012345678901234567890'), '[redacted]');
});
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/redaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const ASSIGNED_SECRET_KEY_VALUE_PATTERN =
/\b(([A-Za-z][A-Za-z0-9_-]*)(?:[ \t]|\\\r?\n)*[:=](?:[ \t]|\\\r?\n)*['"]?)(?:\\\r?\n|[^\s"'&<>])+/g;
const AUTHORIZATION_HEADER_PATTERN =
/(^|[^A-Za-z0-9_])(['"]?(?:proxy[-_]?authorization|authorization)['"]?\s*:\s*['"]?(?:bearer|basic|token)\s+)[^\s"'<>]+/gim;
const STANDALONE_BEARER_PATTERN = /\b(Bearer\s+)[A-Za-z0-9._~+/-]+=*/gi;
const AWS_CLI_SPACE_SECRET_PATTERN = new RegExp(
`(^|[\\s;&|()])((?:aws${SHELL_SEPARATOR_SOURCE}configure${SHELL_SEPARATOR_SOURCE}set${SHELL_SEPARATOR_SOURCE}${AWS_CONFIG_SECRET_KEY_SOURCE}|${AWS_SECRET_ACCESS_KEY_FLAG_SOURCE})${SHELL_SEPARATOR_SOURCE})${SHELL_SECRET_TOKEN_SOURCE}`,
'gm',
Expand Down Expand Up @@ -78,6 +79,7 @@ function redactTextSecrets(value: string): string {
AUTHORIZATION_HEADER_PATTERN,
(_match, boundary: string, prefix: string) => `${boundary}${prefix}[redacted]`,
);
next = next.replace(STANDALONE_BEARER_PATTERN, (_match, prefix: string) => `${prefix}[redacted]`);
next = next.replace(
AWS_CLI_SPACE_SECRET_PATTERN,
(_match, boundary: string, prefix: string, token: string) =>
Expand Down