Skip to content

Commit

Permalink
Merge pull request #1335 from airbrake/784-allowlist
Browse files Browse the repository at this point in the history
browser/src/jsonify_notice: add the keysAllowlist option
  • Loading branch information
kyrylo authored Dec 8, 2022
2 parents 9072743 + 1d689f9 commit 96256a5
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 9 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

### master

#### browser

- Added the `keysAllowlist` option, which is a counter-part to the
`keysBlocklist` option. It filters out all the data from the notice except the
specified keys
([#1335](https://github.com/airbrake/airbrake-js/pull/1335))

### [2.1.8] (December 6, 2022)

#### browser
Expand Down
18 changes: 18 additions & 0 deletions packages/browser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ airbrake.addFilter((notice) => {

### Filtering keys

#### keysBlocklist

With the `keysBlocklist` option, you can specify a list of keys containing
sensitive information that must be filtered out:

Expand All @@ -211,6 +213,22 @@ const airbrake = new Notifier({
});
```

#### keysAllowlist

With the `keysAllowlist` option, you can specify a list of keys that that should
_not_ be filtered. All other keys will be substituted with the `[Filtered]`
label.

```js
const airbrake = new Notifier({
// ...
keysAllowlist: [
'email', // exact match
/name/, // regexp match
],
});
```

### Source maps

Airbrake supports using private and public source maps. Check out our docs for
Expand Down
31 changes: 22 additions & 9 deletions packages/browser/src/jsonify_notice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ const MAX_OBJ_LENGTH = 128;
// environment and session keys.
export function jsonifyNotice(
notice: INotice,
{ maxLength = 64000, keysBlocklist = [] } = {}
{ maxLength = 64000, keysBlocklist = [], keysAllowlist = [] } = {}
): string {
if (notice.errors) {
for (let i = 0; i < notice.errors.length; i++) {
let t = new Truncator({ keysBlocklist });
let t = new Truncator({ keysBlocklist, keysAllowlist });
notice.errors[i] = t.truncate(notice.errors[i]);
}
}

let s = '';
let keys = ['params', 'environment', 'session'];
for (let level = 0; level < 8; level++) {
let opts = { level, keysBlocklist };
let opts = { level, keysBlocklist, keysAllowlist };
for (let key of keys) {
let obj = notice[key];
if (obj) {
Expand Down Expand Up @@ -61,6 +61,7 @@ function scale(num: number, level: number): number {
interface ITruncatorOptions {
level?: number;
keysBlocklist?: any[];
keysAllowlist?: any[];
}

class Truncator {
Expand All @@ -71,11 +72,13 @@ class Truncator {

private keys: string[] = [];
private keysBlocklist: any[] = [];
private keysAllowlist: any[] = [];
private seen: any[] = [];

constructor(opts: ITruncatorOptions) {
let level = opts.level || 0;
this.keysBlocklist = opts.keysBlocklist || [];
this.keysAllowlist = opts.keysAllowlist || [];

this.maxStringLength = scale(this.maxStringLength, level);
this.maxObjectLength = scale(this.maxObjectLength, level);
Expand Down Expand Up @@ -192,10 +195,8 @@ class Truncator {
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
continue;
}
if (isBlocklisted(key, this.keysBlocklist)) {
dst[key] = FILTERED;
continue;
}

if (this.filterKey(key, dst)) continue;

let value = getAttr(obj, key);

Expand All @@ -211,6 +212,18 @@ class Truncator {
}
return dst;
}

private filterKey(key: string, obj: any): boolean {
if (
(this.keysAllowlist.length > 0 && !isInList(key, this.keysAllowlist)) ||
(this.keysAllowlist.length === 0 && isInList(key, this.keysBlocklist))
) {
obj[key] = FILTERED;
return true;
}

return false;
}
}

export function truncate(value: any, opts: ITruncatorOptions = {}): any {
Expand All @@ -232,8 +245,8 @@ function objectType(obj: any): string {
return s.slice('[object '.length, -1);
}

function isBlocklisted(key: string, keysBlocklist: any[]): boolean {
for (let v of keysBlocklist) {
function isInList(key: string, list: any[]): boolean {
for (let v of list) {
if (v === key) {
return true;
}
Expand Down
68 changes: 68 additions & 0 deletions packages/browser/tests/jsonify_notice.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,72 @@ describe('jsonify_notice', () => {
});
});
});

describe('keysAllowlist', () => {
describe('when the allowlist key is a string', () => {
const notice = {
params: { name: 'I am allowlisted', email: 'I will be filtered' },
session: { session1: 'I will be filtered, too' },
context: { notifier: { name: 'I am allowlisted' } },
};
let json;

beforeEach(() => {
json = jsonifyNotice(notice, { keysAllowlist: ['name'] });
});

it('filters out everything but allowlisted keys', () => {
expect(JSON.parse(json)).toStrictEqual({
params: { name: 'I am allowlisted', email: '[Filtered]' },
session: { session1: '[Filtered]' },
context: { notifier: { name: 'I am allowlisted' } },
});
});
});

describe('when the allowlist key is a regexp', () => {
const notice = {
params: { name: 'I am allowlisted', email: 'I will be filtered' },
session: { session1: 'I will be filtered, too' },
context: { notifier: { name: 'I am allowlisted' } },
};
let json;

beforeEach(() => {
json = jsonifyNotice(notice, { keysAllowlist: [/nam/] });
});

it('filters out everything but allowlisted keys', () => {
expect(JSON.parse(json)).toStrictEqual({
params: { name: 'I am allowlisted', email: '[Filtered]' },
session: { session1: '[Filtered]' },
context: { notifier: { name: 'I am allowlisted' } },
});
});
});
});

describe('when called both with a blocklist and an allowlist', () => {
const notice = {
params: { name: 'Name' },
session: { session1: 'value1' },
context: { notifier: { name: 'airbrake-js' } },
};
let json;

beforeEach(() => {
json = jsonifyNotice(notice, {
keysBlocklist: ['name'],
keysAllowlist: ['name'],
});
});

it('ignores the blocklist and uses the allowlist', () => {
expect(JSON.parse(json)).toStrictEqual({
params: { name: 'Name' },
session: { session1: '[Filtered]' },
context: { notifier: { name: 'airbrake-js' } },
});
});
});
});

0 comments on commit 96256a5

Please sign in to comment.