• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

prebid / Prebid.js / 19836249188

01 Dec 2025 08:19PM UTC coverage: 96.234% (+0.004%) from 96.23%
19836249188

push

github

web-flow
clickioBidAdapter: add IAB GVL ID and TCFEU support (#14224)

53490 of 65500 branches covered (81.66%)

1 of 1 new or added line in 1 file covered. (100.0%)

87 existing lines in 10 files now uncovered.

204130 of 212118 relevant lines covered (96.23%)

72.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.98
/libraries/objectGuard/objectGuard.js
1
import {isData, sessionedApplies} from '../../src/activities/redactor.js';
2
import {deepEqual, logWarn} from '../../src/utils.js';
3

4
/**
5
 * @typedef {import('../src/activities/redactor.js').TransformationRuleDef} TransformationRuleDef
6
 * @typedef {import('../src/adapters/bidderFactory.js').TransformationRule} TransformationRule
7
 * @typedef {Object} ObjectGuard
8
 * @property {*} obj a view on the guarded object
9
 * @property {function(): void} verify a function that checks for and rolls back disallowed changes to the guarded object
10
 */
11

12
/**
13
 * Create a factory function for object guards using the given rules.
14
 *
15
 * An object guard is a view on the guarded object that applies "redact" rules (the same rules used in activites/redactor.js),
16
 * and prevents writes (including deltes) that violate "write protect" rules.
17
 *
18
 * This is meant to provide sandboxed version of a privacy-sensitive object, where reads
19
 * are filtered through redaction rules and writes are checked against write protect rules.
20
 *
21
 */
22
export function objectGuard(rules) {
23
  const root = {};
91✔
24

25
  // rules are associated with specific portions of the object, e.g. "user.eids"
26
  // build a tree representation of them, where the root is the object itself,
27
  // and each node's children are properties of the corresponding (nested) object.
28

29
  function invalid() {
30
    return new Error('incompatible redaction rules');
4✔
31
  }
32

33
  rules.forEach(rule => {
91✔
34
    rule.paths.forEach(path => {
490✔
35
      let node = root;
1,900✔
36
      path.split('.').forEach(el => {
1,900✔
37
        node.children = node.children ?? {};
4,402✔
38
        node.children[el] = node.children[el] ?? { parent: node, path: node.path ? `${node.path}.${el}` : el };
4,402✔
39
        node = node.children[el];
4,402✔
40
        node.wpRules = node.wpRules ?? [];
4,402✔
41
        node.redactRules = node.redactRules ?? [];
4,402✔
42
      });
43
      const tag = rule.wp ? 'hasWP' : 'hasRedact';
1,900✔
44
      const ruleset = rule.wp ? 'wpRules' : 'redactRules';
1,900✔
45
      // sanity check: do not allow rules of the same type on related paths,
46
      // e.g. redact both 'user' and 'user.eids'; we don't need and this logic
47
      // does not handle it
48
      if (node[tag] && !node[ruleset]?.length) {
1,900✔
49
        throw invalid();
2✔
50
      }
51
      node[ruleset].push(rule);
1,898✔
52
      let parent = node;
1,898✔
53
      while (parent) {
1,898✔
54
        parent[tag] = true;
6,296✔
55
        if (parent !== node && parent[ruleset]?.length) {
6,296✔
56
          throw invalid();
2✔
57
        }
58
        parent = parent.parent;
6,294✔
59
      }
60
    });
61
  });
62

63
  function getRedactRule(node) {
64
    if (node.redactRule == null) {
332✔
65
      node.redactRule = node.redactRules.length === 0 ? false : {
148✔
66
        check: (applies) => node.redactRules.some(applies),
169✔
67
        get(val) {
68
          for (const rule of node.redactRules) {
12✔
69
            val = rule.get(val);
13✔
70
            if (!isData(val)) break;
13✔
71
          }
72
          return val;
12✔
73
        }
74
      }
75
    }
76
    return node.redactRule;
332✔
77
  }
78

79
  function getWPRule(node) {
80
    if (node.wpRule == null) {
138✔
81
      node.wpRule = node.wpRules.length === 0 ? false : {
78✔
82
        check: (applies) => node.wpRules.some(applies),
129✔
83
      }
84
    }
85
    return node.wpRule;
138✔
86
  }
87

88
  /**
89
   * clean up `newValue` so that it doesn't violate any write protect rules
90
   * when set onto the property represented by 'node'.
91
   *
92
   * This is done substituting (portions of) `curValue` when some rule is violated.
93
   */
94
  function cleanup(node, curValue, newValue, applies) {
95
    if (
101✔
96
      !node.hasWP ||
314✔
97
      (!isData(curValue) && !isData(newValue)) ||
98
      deepEqual(curValue, newValue)
99
    ) {
100
      return newValue;
79✔
101
    }
102
    const rule = getWPRule(node);
22✔
103
    if (rule && rule.check(applies)) {
22✔
104
      return curValue;
14✔
105
    }
106
    if (node.children) {
8✔
107
      for (const [prop, child] of Object.entries(node.children)) {
8✔
108
        const propValue = cleanup(child, curValue?.[prop], newValue?.[prop], applies);
30✔
109
        if (newValue != null && typeof newValue === 'object') {
30✔
110
          if (!isData(propValue) && !curValue?.hasOwnProperty(prop)) {
29✔
111
            delete newValue[prop];
26✔
112
          } else {
113
            newValue[prop] = propValue;
3✔
114
          }
115
        } else {
116
          logWarn(`Invalid value set for '${node.path}', expected an object`, newValue);
1✔
117
          return curValue;
1✔
118
        }
119
      }
120
    }
121
    return newValue;
7✔
122
  }
123

124
  function isDeleteAllowed(node, curValue, applies) {
125
    if (!node.hasWP || !isData(curValue)) {
5✔
126
      return true;
1✔
127
    }
128
    const rule = getWPRule(node);
4✔
129
    if (rule && rule.check(applies)) {
4✔
130
      return false;
3✔
131
    }
132
    if (node.children) {
1✔
133
      for (const [prop, child] of Object.entries(node.children)) {
1✔
134
        if (!isDeleteAllowed(child, curValue?.[prop], applies)) {
2!
135
          return false;
1✔
136
        }
137
      }
138
    }
UNCOV
139
    return true;
×
140
  }
141

142
  function mkGuard(obj, tree, final, applies, cache = new WeakMap()) {
568✔
143
    // If this object is already proxied, return the cached proxy
144
    if (cache.has(obj)) {
568✔
145
      return cache.get(obj);
211✔
146
    }
147

148
    const proxy = new Proxy(obj, {
357✔
149
      get(target, prop, receiver) {
150
        const val = Reflect.get(target, prop, receiver);
1,107✔
151
        if (final && val != null && typeof val === 'object') {
1,107✔
152
          // a parent property has write protect rules, keep guarding
153
          return mkGuard(val, tree, final, applies, cache)
159✔
154
        } else if (tree.children?.hasOwnProperty(prop)) {
948✔
155
          const {children, hasWP} = tree.children[prop];
335✔
156
          if (isData(val)) {
335✔
157
            // if this property has redact rules, apply them
158
            const rule = getRedactRule(tree.children[prop]);
332✔
159
            if (rule && rule.check(applies)) {
332✔
160
              return rule.get(val);
12✔
161
            }
162
          }
163
          if ((children || hasWP) && val != null && typeof val === 'object') {
323✔
164
            // some nested properties have rules, return a guard for the branch
165
            return mkGuard(val, tree.children?.[prop] || tree, final || children == null, applies, cache);
316!
166
          }
167
        }
168
        return val;
620✔
169
      },
170
      set(target, prop, newValue, receiver) {
171
        if (final) {
181✔
172
          // a parent property has rules, apply them
173
          const rule = getWPRule(tree);
107✔
174
          if (rule && rule.check(applies)) {
107✔
175
            return true;
56✔
176
          }
177
        }
178
        if (tree.children?.hasOwnProperty(prop)) {
125✔
179
          // apply all (possibly nested) write protect rules
180
          const curValue = Reflect.get(target, prop, receiver);
71✔
181
          newValue = cleanup(tree.children[prop], curValue, newValue, applies);
71✔
182
          if (!isData(newValue) && !target.hasOwnProperty(prop)) {
71✔
183
            return true;
7✔
184
          }
185
        }
186
        return Reflect.set(target, prop, newValue, receiver);
118✔
187
      },
188
      deleteProperty(target, prop) {
189
        if (final) {
8✔
190
          // a parent property has rules, apply them
191
          const rule = getWPRule(tree);
5✔
192
          if (rule && rule.check(applies)) {
5✔
193
            return true;
4✔
194
          }
195
        }
196
        if (tree.children?.hasOwnProperty(prop) && !isDeleteAllowed(tree.children[prop], target[prop], applies)) {
4✔
197
          // some nested properties should not be deleted
198
          return true;
3✔
199
        }
200
        return Reflect.deleteProperty(target, prop);
1✔
201
      }
202
    });
203

204
    // Cache the proxy before returning
205
    cache.set(obj, proxy);
357✔
206
    return proxy;
357✔
207
  }
208

209
  return function guard(obj, ...args) {
192✔
210
    const session = {};
93✔
211
    return mkGuard(obj, root, false, sessionedApplies(session, ...args))
93✔
212
  };
213
}
214

215
/**
216
 * @param {TransformationRuleDef} ruleDef
217
 * @return {TransformationRule}
218
 */
219
export function writeProtectRule(ruleDef) {
220
  return Object.assign({
135✔
221
    wp: true,
222
  }, ruleDef)
223
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc