10
11
'use strict';
12
13
+const heldValues = [];
14
+let finalizationCallback;
15
+function FinalizationRegistryMock(callback) {
16
+ finalizationCallback = callback;
17
+}
18
+FinalizationRegistryMock.prototype.register = function (target, heldValue) {
19
+ heldValues.push(heldValue);
20
+};
21
+global.FinalizationRegistry = FinalizationRegistryMock;
22
+
23
+function gc() {
24
+ for (let i = 0; i < heldValues.length; i++) {
25
+ finalizationCallback(heldValues[i]);
26
+ }
27
+ heldValues.length = 0;
28
+}
29
+
30
let act;
31
let use;
32
let startTransition;
1463
);
1464
});
1465
});
1466
+
1467
+ // @gate enableTaint
1468
+ it('errors when a tainted object is serialized', async () => {
1469
+ function UserClient({user}) {
1470
+ return <span>{user.name}</span>;
1471
+ }
1472
+ const User = clientReference(UserClient);
1473
+
1474
+ const user = {
1475
+ name: 'Seb',
1476
+ age: 'rather not say',
1477
+ };
1478
+ ReactServer.experimental_taintObjectReference(
1479
+ "Don't pass the raw user object to the client",
1480
+ user,
1481
+ );
1482
+ const errors = [];
1483
+ ReactNoopFlightServer.render(<User user={user} />, {
1484
+ onError(x) {
1485
+ errors.push(x.message);
1486
+ },
1487
+ });
1488
+
1489
+ expect(errors).toEqual(["Don't pass the raw user object to the client"]);
1490
+ });
1491
+
1492
+ // @gate enableTaint
1493
+ it('errors with a specific message when a tainted function is serialized', async () => {
1494
+ function UserClient({user}) {
1495
+ return <span>{user.name}</span>;
1496
+ }
1497
+ const User = clientReference(UserClient);
1498
+
1499
+ function change() {}
1500
+ ReactServer.experimental_taintObjectReference(
1501
+ 'A change handler cannot be passed to a client component',
1502
+ change,
1503
+ );
1504
+ const errors = [];
1505
+ ReactNoopFlightServer.render(<User onChange={change} />, {
1506
+ onError(x) {
1507
+ errors.push(x.message);
1508
+ },
1509
+ });
1510
+
1511
+ expect(errors).toEqual([
1512
+ 'A change handler cannot be passed to a client component',
1513
+ ]);
1514
+ });
1515
+
1516
+ // @gate enableTaint
1517
+ it('errors when a tainted string is serialized', async () => {
1518
+ function UserClient({user}) {
1519
+ return <span>{user.name}</span>;
1520
+ }
1521
+ const User = clientReference(UserClient);
1522
+
1523
+ const process = {
1524
+ env: {
1525
+ SECRET: '3e971ecc1485fe78625598bf9b6f85db',
1526
+ },
1527
+ };
1528
+ ReactServer.experimental_taintUniqueValue(
1529
+ 'Cannot pass a secret token to the client',
1530
+ process,
1531
+ process.env.SECRET,
1532
+ );
1533
+
1534
+ const errors = [];
1535
+ ReactNoopFlightServer.render(<User token={process.env.SECRET} />, {
1536
+ onError(x) {
1537
+ errors.push(x.message);
1538
+ },
1539
+ });
1540
+
1541
+ expect(errors).toEqual(['Cannot pass a secret token to the client']);
1542
+
1543
+ // This just ensures the process object is kept alive for the life time of
1544
+ // the test since we're simulating a global as an example.
1545
+ expect(process.env.SECRET).toBe('3e971ecc1485fe78625598bf9b6f85db');
1546
+ });
1547
+
1548
+ // @gate enableTaint
1549
+ it('errors when a tainted bigint is serialized', async () => {
1550
+ function UserClient({user}) {
1551
+ return <span>{user.name}</span>;
1552
+ }
1553
+ const User = clientReference(UserClient);
1554
+
1555
+ const currentUser = {
1556
+ name: 'Seb',
1557
+ token: BigInt('0x3e971ecc1485fe78625598bf9b6f85dc'),
1558
+ };
1559
+ ReactServer.experimental_taintUniqueValue(
1560
+ 'Cannot pass a secret token to the client',
1561
+ currentUser,
1562
+ currentUser.token,
1563
+ );
1564
+
1565
+ function App({user}) {
1566
+ return <User token={user.token} />;
1567
+ }
1568
+
1569
+ const errors = [];
1570
+ ReactNoopFlightServer.render(<App user={currentUser} />, {
1571
+ onError(x) {
1572
+ errors.push(x.message);
1573
+ },
1574
+ });
1575
+
1576
+ expect(errors).toEqual(['Cannot pass a secret token to the client']);
1577
+ });
1578
+
1579
+ // @gate enableTaint && enableBinaryFlight
1580
+ it('errors when a tainted binary value is serialized', async () => {
1581
+ function UserClient({user}) {
1582
+ return <span>{user.name}</span>;
1583
+ }
1584
+ const User = clientReference(UserClient);
1585
+
1586
+ const currentUser = {
1587
+ name: 'Seb',
1588
+ token: new Uint32Array([0x3e971ecc, 0x1485fe78, 0x625598bf, 0x9b6f85dd]),
1589
+ };
1590
+ ReactServer.experimental_taintUniqueValue(
1591
+ 'Cannot pass a secret token to the client',
1592
+ currentUser,
1593
+ currentUser.token,
1594
+ );
1595
+
1596
+ function App({user}) {
1597
+ const clone = user.token.slice();
1598
+ return <User token={clone} />;
1599
+ }
1600
+
1601
+ const errors = [];
1602
+ ReactNoopFlightServer.render(<App user={currentUser} />, {
1603
+ onError(x) {
1604
+ errors.push(x.message);
1605
+ },
1606
+ });
1607
+
1608
+ expect(errors).toEqual(['Cannot pass a secret token to the client']);
1609
+ });
1610
+
1611
+ // @gate enableTaint
1612
+ it('keep a tainted value tainted until the end of any pending requests', async () => {
1613
+ function UserClient({user}) {
1614
+ return <span>{user.name}</span>;
1615
+ }
1616
+ const User = clientReference(UserClient);
1617
+
1618
+ function getUser() {
1619
+ const user = {
1620
+ name: 'Seb',
1621
+ token: '3e971ecc1485fe78625598bf9b6f85db',
1622
+ };
1623
+ ReactServer.experimental_taintUniqueValue(
1624
+ 'Cannot pass a secret token to the client',
1625
+ user,
1626
+ user.token,
1627
+ );
1628
+ return user;
1629
+ }
1630
+
1631
+ function App() {
1632
+ const user = getUser();
1633
+ const derivedValue = {...user};
1634
+ // A garbage collection can happen at any time. Even before the end of
1635
+ // this request. This would clean up the user object.
1636
+ gc();
1637
+ // We should still block the tainted value.
1638
+ return <User user={derivedValue} />;
1639
+ }
1640
+
1641
+ let errors = [];
1642
+ ReactNoopFlightServer.render(<App />, {
1643
+ onError(x) {
1644
+ errors.push(x.message);
1645
+ },
1646
+ });
1647
+
1648
+ expect(errors).toEqual(['Cannot pass a secret token to the client']);
1649
+
1650
+ // After the previous requests finishes, the token can be rendered again.
1651
+
1652
+ errors = [];
1653
+ ReactNoopFlightServer.render(
1654
+ <User user={{token: '3e971ecc1485fe78625598bf9b6f85db'}} />,
1655
+ {
1656
+ onError(x) {
1657
+ errors.push(x.message);
1658
+ },
1659
+ },
1660
+ );
1661
+
1662
+ expect(errors).toEqual([]);
1663
+ });
1664
});