---
# System prepended metadata

title: Client side security - My Practice

---

# Client side security - My Practice

К решению прикладывайте скриншоты и код, который добавили в `client-side-playground`.
Можете оставить комментарии к результатам, например, почему произошла ошибка. 

## 1. SOP

### 1.1 `iframe` / window

Вместо `iframe` можно открывать новое окно.

- [ ] Открыть `iframe` со страницей на **том же** `Origin`.
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Page with Frame</title>
    <script>
        function onShowFrameButtonClick(){
            alert(document.getElementsByTagName("iframe")[0].contentDocument.documentElement.outerHTML);
        }
    </script>
</head>
<body>
    This page contains iframe:
    <br>
    <form>
        <input type="button" value="Show IFrame Content" onclick="onShowFrameButtonClick();">
    </form>
    <br>
    <iframe src="frame1.html"/> <br>
</body>
</html>
```
- [ ] Прочитать содержимое `iframe` из Javascript.
![](https://i.imgur.com/QO1zSV7.png)

- [ ] Открыть `iframe` со страницей на **другом** `Origin`.
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Page with Frame</title>
    <script>
        function onShowFrameButtonClick(){
            alert(document.getElementsByTagName("iframe")[0].contentDocument.documentElement.outerHTML);
        }
    </script>
</head>
<body>
    This page contains iframe:
    <br>
    <form>
        <input type="button" value="Show IFrame Content" onclick="onShowFrameButtonClick();">
    </form>
    <br>
    <iframe src="https://a.hu.io/ui/frame1.html"/> <br>
</body>
</html>
```
- [ ] Прочитать содержимое `iframe` из Javascript.
![](https://i.imgur.com/lC3hs88.png)

Это запрещено Same Origin Policy

### 1.2 inherited origins

- [ ] С основной страницы открыть окно `about:blank` и посмотреть `Origin` (`document.domain`).
```
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>Task 1.2</title>
</head>
<body>
  <input type="button" onclick="let w = window.open('about:blank'); alert(w.document.domain);" value="Open Empty Window" ></input>
</body>
</html>
```

![](https://i.imgur.com/UDrPpgi.png)

- [ ] С основной страницы открыть окно со схемой `data:` и посмотреть `Origin`.

Не удалось открыть новое окно со схемой `data:`: всё, что я ни пробовал, либо открывает вкладку со страницей about:blank, либо вообще не открывает ничего

Например, это происходит при нажатии кнопки Open Data Window на этой страничке:
```
!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>Task 1.2</title>
    <script>
        var w;
        function Open()
        {
            w = window.open('data:,tra-ta-ta');
        }
        function Check(){
            alert(w.document.domain);
        }
    </script>
</head>
<body>
  <input type="button" onclick="Open();" value="Open Data Window" >
  <input type="button" onclick="Check();" value="Check Domain" >
</body>
</html>
```

При нажатии кнопки Check Domain показывается домен начальной страницы:

![](https://i.imgur.com/KF1shF8.png)


### 1.3 changing origin

- [ ] Открыть страницу с одного поддомена (`a.hu.io`).
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Task 1.3</title>
    <script>
        var wnd;
        function OpenWindow(){
            wnd = window.open("https://b.hu.io/ui/task1.3_to_new_tab.html");
        }
        function CheckAccess(){
            alert(wnd.document.title);
        }
        function ChangeDomain(){
            document.domain = "hu.io";
        }
    </script>
</head>
<body>
    <form>
        <input type="button" value="Открыть страницу с другого источника" onclick="OpenWindow();"></input>
        <br>
        <input type="button" value="Проверить доступность другого окна" onClick="CheckAccess();"></input>
        <br>
        <input type="button" value="Сменить домен" onclick="ChangeDomain();"></input>
    </form>
</body>
</html>
```
![](https://i.imgur.com/g3dEH6x.png)

- [ ] С первой страницы открыть другую со второго домена (`b.hu.io`).
![](https://i.imgur.com/ixA3ZA4.png)

- [ ] Проверить взаимную доступность DOM через `Window` объект и `window.opener` соответственно.
![](https://i.imgur.com/Cdxbead.png)
![](https://i.imgur.com/7zDXszv.png)

- [ ] Провести смену домена (`document.domain = 'hu.io'`) и повторитть проверку.
![](https://i.imgur.com/zH14JDy.png)
![](https://i.imgur.com/kUcl8J1.png)


- [ ] Открыть страницу с `Feature-Policy: document-domain 'none';` и попробовать сменить домен.
```
@collect_handler
def no_domain_setup(request: Request, response: Response):
    value = request.query_params.get('NoDomainSetup', None)
    if value is None:
        return {}
    else:
        return {"Feature-Policy": "document-domain 'none';"}
```
Заголовок возымел действие только в Хроме:
![](https://i.imgur.com/njaomWL.png)


### 1.4 interpage communication

- [ ] Сделать страницу, которая слушает сообщения и возвращает их обратно через `postMessage`.
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>1.4 Ping Pong Page</title>
    <script>
        window.addEventListener("message", (event) => {
            event.source.postMessage(event.data, "*");
        },
        false);
    </script>
</head>
<body>
</body>
</html>
```
- [ ] Обеспечить, чтобы обратное сообщение получила страница с тем же `Origin`.
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>1.4 Ping Pong Page Only to This Origin</title>
    <script>
        window.addEventListener("message", (event) => {
            event.source.postMessage(event.data, "https://" + document.domain);
        },
        false);
    </script>
</head>
<body>
</body>
</html>
```
![](https://i.imgur.com/svEYjnb.png)

### 1.5 Cross-Origin-Opener-Policy

- [ ] Сделать страницу, которая возвращает заголовок `Cross-Origin-Opener-Policy: same-origin`.

Заголовок:
```
@collect_handler
def coop_setup(request: Request, response: Response):
    if str(request.url).endswith("task1.5.html"):
        return {"Cross-Origin-Opener-Policy": "same-origin"}
    else:
        return {}
```
Сама страничка:
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Task 1.5</title>
</head>
<body>
    <form>
        <input type="button" value="Check Opener" onclick="alert(window.opener);"></input>
    </form>
</body>
</html>
```


- [ ] Открыть эту страницу с другой страницы с **таким же** `Origin` и отличающимся.

Код открывающей страницы:
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Task 1.5 Opener</title>
    <script>
        var wnd;
        function OpenFromSameOrigin(){
            wnd = window.open("https://hu.io/ui/task1.5.html");
        }
        function OpenFromOtherOrigin(){
            wnd = window.open("https://a.hu.io/ui/task1.5.html");
        }
        function CheckWnd(){
            alert(wnd.document);
        }
    </script>
</head>
<body>
    <form>
        <input type="button" value="Open Page From the Same Origin" onclick="OpenFromSameOrigin();"></input>
        <br>
        <input type="button" value="Open Page From the Other Origin" onclick="OpenFromOtherOrigin();"></input>
        <br>
        <input type="button" value="Check Opened Document" onclick="CheckWnd();"></input>
    </form>
</body>
</html>
```

- [ ] В обоих случаях посмотреть на `Window` объект и значение `window.opener`.

Различия в поведении в зависимости от origin мною не замечено:

![](https://i.imgur.com/su2fzm7.png)

![](https://i.imgur.com/gJgocZK.png)

Если честно, сколько ни пересматривал объяснение, сколько ни вчитывался в англоязычные обороты, так и не смог понять, зачем нужен этот заголовок. Вроде бы SOP и так должен запрещать доступ к документам из разных origin.


## 2. CORS

### 2.1 forms

- [ ] Сделать форму, которая отправляет POST запрос на другой `Origin`.
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Task 2.1</title>
</head>
<body>
  <form action="https://a.hu.io/subscribe.asp">
      Name:
      <input type="text" name="name"></input>
      <br>
      E-mail:
      <input type="email" name="email"></input>
      <br>
      <input type="submit" value="Subscribe">
  </form>

</body>
</html>
```
- [ ] Посмотреть на предаваемые в запросе заголовки. 
  ![](https://i.imgur.com/0gGcp3R.png)



### 2.2 simple

- [ ] Выставить куки на домене `b.hu.io`.
```
@collect_handler
def set_cookie(request: Request, response: Response):
    if str(request.url).endswith("hu.io/ui/task2.2_setCookie.html"):
        return {"Set-Cookie": "task2.2_cookie=someValue; SameSite=None; Path=/"}
    else:
        if(str(request.url)).endswith("hu.io/ui/task2.3_setCookie.html"):
            return {"Set-Cookie": "subscr_id=25; SameSite=None; Path=/"}
        else:
            return {}

```
![](https://i.imgur.com/n5aPCHD.png)


- [ ] Реализовать эндпоинт-обработчик POST запроса, который возвращает:
    - `Access-Control-Allow-Origin: <request_origin>`

Реализована выдача такого ответа в случае передачи query-параметра cors=1
```
@collect_handler
def set_cors(request: Request, response: Response):
    value = request.query_params.get("cors", "0")
    origin = request.headers.get("Origin", "")
    if value == "1":
        return {"Access-Control-Allow-Origin": origin}
    else:
        if value == "2":
            return {"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true"}
        else:
            if value == "3":
                return {"Access-Control-Allow-Origin": origin, "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Headers": "Content-Type"}
            else:
                return {}

```
- [ ] Сделать запрос с `a.hu.io` на этот эндпоинт.
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Task 2.2</title>
    <script>
        function DoRequest(cors_mode){
            let r = new XMLHttpRequest();
            r.open('POST', "https://b.hu.io/task2/subscribe?cors=" + cors_mode, false);
            r.withCredentials = document.getElementById("wc").checked;
            r.setRequestHeader('Content-Type', 'text/plain');
            r.send("Subscribe me, please!!!");
            alert(r.response);
        }
    </script>
</head>
<body>
    <form>
        <input type="button" value="Get Response With ACAO Header" onclick="DoRequest('1');"></input>
        <br>
        <input type="button" value="Get Response Without ACAO=* and ACAC Headers" onclick="DoRequest('2');"></input>
        <br>
        <input type="checkbox" name="wc" id="wc"> </input>
        <label for="wc">withCredentials for the XMLHttpRequest</label>
    </form>
</body>
</html>
```

- [ ] Посмотреть, были ли присланы куки.

Кука была отправлена, но браузер заблокировал ответ:
![](https://i.imgur.com/4dB61MY.png)


- [ ] Реализовать эндпоинт-обработчик POST запроса, который возвращает:
    - `Access-Control-Allow-Origin: *`
    - `Access-Control-Allow-Credentials: true`

Сделано в функции set_cors (см. выше). Такой ответ будет в случае передачи query-параметра cors=2

- [ ] Сделать запрос с `a.hu.io` на этот эндпоинт.

Кука были отправлена, но браузер снова заблокировал ответ:
![](https://i.imgur.com/MKxtrX5.png)


- [ ] Описать ошибку из консоли.
Причина понятна: это противоречивые заголовки.
access-control-allow-credentials: true допустим только при указании конкретного домена в заголовке
access-control-allow-origin

### 2.3 preflight

- [ ] Реализовать эндпоинт, отвечающий на методы `OPTIONS` и `POST`. На `POST` **только с кукой**.

```
@app.post("/task2/data")
async def subscription_data(subscr_id: Optional[str] = Cookie(None)):
    if subscr_id:
        return {"subscription": True, "result": "Data were taken."}
    else:
        raise HTTPException(401, "Unauthorized")


@app.options("/task2/data")
async def subscription_data_preflight():
    return ""

```

- [ ] Настроить хедеры в ответах так, чтобы запрос с другого `Origin` успешно отрабатывал и получал JSON-значение в ответ. (Разрешение кросс-сайт авторизованных запросов)

Это реализовано в ф-ции set_cors. Приведу её здесь вновь:

```
@collect_handler
def set_cors(request: Request, response: Response):
    value = request.query_params.get("cors", "0")
    origin = request.headers.get("Origin", "")
    if value == "1":
        return {"Access-Control-Allow-Origin": origin}
    else:
        if value == "2":
            return {"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true"}
        else:
            if value == "3":
                return {"Access-Control-Allow-Origin": origin, "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Headers": "Content-Type"}
            else:
                return {}


```
Сначала установка куки на b.hu.io:
![](https://i.imgur.com/H2uKPj1.png)

Теперь собственно cors-запрос с a.hu.io:

![](https://i.imgur.com/a6Hpfhk.png)


## 3. CSP

### 3.1 default

- [ ] Реализовать страницу, на которую можно загружать ресурсы только с того же `Origin`, а картинки из любого места.

```
@collect_handler
def csp_only_self_except_images(request: Request, response: Response):
    if str(request.url).endswith("task3.1.html"):
        return {"Content-Security-Policy": "default-src 'self'; img-src *"}
    else:
        return {}

```

```
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Task 3.1</title>
</head>
<body>
    <audio controls width="250">
        <source src="https://b.hu.io/ui/07.mp3" type="audio/mpeg">
    </audio>
    <br>
    <img src="https://b.hu.io/ui/IMG_3.JPG"/>
</body>
</html>
```

- [ ] Предъявить CSP.

![](https://i.imgur.com/qu5MX3y.png)


### 3.2 unsafe-inline

- [ ] Реализовать страницу, на которую нельзя ничего загружать, но можно использовать inline стили и скрипты.

```
@collect_handler
def only_inline_scripts(request: Request, response: Response):
    if str(request.url).endswith("task3.2.html"):
        return {"Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'"}
    else:
        return {}

```

```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Task 3.2</title>
</head>
<body>
  <form>
      <input type="button" value="Check Script" onclick="alert('Inline scripts work');">
  </form>
  <br>
  Image: <br>
  <img src="IMG_3.JPG"/>

</body>
</html>
```

- [ ] Предъявить CSP.
![](https://i.imgur.com/CiZYHUF.png)


### 3.3 connect-src

- [ ] Разрешить обращения через `Fetch API` только на **другой** `Origin`.

```
@collect_handler
def csp_connect_to_origin(request: Request, response: Response):
    if str(request.url).endswith("task3.3.html"):
        return {"Content-Security-Policy": "connect-src https://a.hu.io"}
    else:
        return {}

```


```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Task 3.3</title>
    <script>
        function TryConnect(){
          var x = new XMLHttpRequest();
          x.open("GET", "https://a.hu.io/ui/task3.1.html?cors=3", false);
          x.send();
          alert(x.response);
        }
        function TryConnectB(){
          var x = new XMLHttpRequest();
          x.open("GET", "https://b.hu.io/ui/task3.1.html?cors=3", false);
          x.send();
          alert(x.response);
        }
    </script>
</head>
<body>
    <form>
        <input type="button" value="Try connect (A)" onclick="TryConnect();"></input>
        <input type="button" value="Try connect (B)" onclick="TryConnectB();"></input>
    </form>

</body>
</html>
```

- [ ] Предъявить CSP.

![](https://i.imgur.com/yuIgCsW.png)



### 3.4 framing

- [ ] Сделать вложенную структуру из фреймов следующего вида:
    1. Главная страница - origin='a.hu.io'.
    2. Фрейм 1 на главной странице - origin='b.hu.io', защиты нет.
    3. Фрейм 2 внутри фрейма 1 - origin='b.hu.io', защита `Same-Origin`.

```
@collect_handler
def csp_frame_from_this_domain(request: Request, response: Response):
    if str(request.url).endswith("task3.4_frame1.html"):
        return {"Content-Security-Policy": "frame-src 'self'"}
    else:
        return {}
```

![](https://i.imgur.com/K4XiHXb.png)


- [ ] Для фрейма 2 разрешить `Same-Origin` фрейминг сначала с помощью `X-Frame-Options`, а потом с помощью `frame-ancestors`. Сравнить результаты.

C X-Frame-Options:

```
@collect_handler
def xfo_framing_from_this_domain(request: Request, response: Response):
    if str(request.url).endswith("task3.4_frame2.html"):
        return {"X-Frame-Options": "SAMEORIGIN"}
    else:
        return {}
```

![](https://i.imgur.com/00AfGcr.png)

C CSP:

```
@collect_handler
def csp_framing_from_this_domain(request: Request, response: Response):
    if str(request.url).endswith("task3.4_frame2.html"):
        return {"Content-Security-Policy": "frame-ancestors 'self'"}
    else:
        return {}
```

![](https://i.imgur.com/LShUofp.png)

Таким образом, в Firefox разницы нет. Объяснение:

![](https://i.imgur.com/RttVpXq.png)


### 3.5 subresource integrity 

- [ ] Вставить скрипт с CDN jquery с `integrity` и `crossorigin` аттрибутами. Проверить, что работает.

![](https://i.imgur.com/Uh40clG.png)


- [ ] Испортить хеш и посмотреть на ошибки.

![](https://i.imgur.com/wy25tdG.png)


- [ ] Убрать атрибуты `integrity` и `crossorigin` и разрешить эту библиотеку через CSP.

Не удалось побороть CSP. Даже подобрал ссылку, где хэш в base64-кодировке не содержит неоднозначных символов, но всё без толку, ошибку так и не смог найти. Буду признателен за подсказку, что не так

```
@collect_handler
def csp_check_script_hash(request: Request, response: Response):
    if str(request.url).endswith("task3.5_csp.html"):
        return {"Content-Security-Policy": "script-src 'sha256-HwWONEZrpuoh951cQD1ov2HUK5zA5DwJ1DNUXaM6FsY='"}
    else:
        return {}
```

![](https://i.imgur.com/uO3VhK8.png)


## 4. Cookies

### 4.1 cookies identity

- [ ] Проверить пример из секции `Cookies identity`. Показать, какие куки прилетают.

```
@app.get("/task4/cart/change")
async def same_name_cookies(response: Response):
    response.set_cookie("name", "A", path="/")
    response.set_cookie("name", "B", path="/task4")
    response.set_cookie("name", "C", path="/task4/cart")
    response.set_cookie("name", "D", path="/task4/cart/change")
    response.set_cookie("name", "E", path="/", domain=".hu.io")
    response.set_cookie("name", "F", path="/task4", domain=".hu.io")
    response.set_cookie("name", "G", path="/task4/cart", domain=".hu.io")
    response.set_cookie("name", "H", path="/task4/cart/change", domain=".hu.io")
    response.set_cookie("name", "I", path="/", domain="a.hu.io")
    response.set_cookie("name", "J", path="/task4", domain="a.hu.io")
    response.set_cookie("name", "K", path="/task4/cart", domain="a.hu.io")
    response.set_cookie("name", "L", path="/task4/cart/change", domain="a.hu.io")
    return {"message": "This page should set many cookies with the same name"}

```

![](https://i.imgur.com/Hm6xQ8J.png)


### 4.2 same-site

- [ ] Выставить разные по значению куки на поддоменах `a.hu.io` и `b.hu.io`, на корень, с `SameSite=Strict`.

```
@app.get("/task4.2/setupCookie")
async def strict_cookies(response: Response):
    response.set_cookie("coo42", "Root", path="/", domain="hu.io", samesite="strict")
    response.set_cookie("coo42", "A", path="/", domain="a.hu.io", samesite="strict")
    response.set_cookie("coo42", "B", path="/", domain="b.hu.io", samesite="strict")
    return {"message": "This page should set many cookies with the same name (coo42)"}

```

Устанавливаю куки на a.hu.io
![](https://i.imgur.com/l4cB53S.png)

Устанавливаю куки на b.hu.io
![](https://i.imgur.com/3e6e5HB.png)



- [ ] Сделать запросы на оба поддомена. Посмотреть какие куки прилетают.

```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    Frame from A subdomain: <br>
    <iframe src="https://a.hu.io/ui/frame1.html"></iframe>
    Frame from B subdomain: <br>
    <iframe src="https://b.hu.io/ui/frame1.html"></iframe>
</body>
</html>
```
Смотрим куки для запроса на поддомен A:
![](https://i.imgur.com/ZtXtljO.png)


Смотрим куки для запроса на поддомен B:

![](https://i.imgur.com/8EYO9ck.png)

С точки зрения атрибута куки SameSite эти запросы не являются кросс-ориджин (ориджином тут являются hu.io  и все его поддомены), поэтому в этих запросах отправляются куки, включая куки с атрибутом SameSite=strict. При этом в каждом запросе отсылаются два значения куки coo42, т.к. значение куки, назначенное для домена hu.io, актуально и для его поддоменов.

### 4.3 history API

- [ ] Выставить разные куки на разные пути.

```
@app.get("/task4.3/setupCookie")
async def cookies_for_two_paths(response: Response):
    response.set_cookie("cooA", "1", path="/ui/task4.3/pathA")
    response.set_cookie("cooB", "2", path="/ui/task4.3/pathB")
    return {"message": "This page should set two cookies"}

```

![](https://i.imgur.com/shbbGvA.png)


- [ ] Поменять путь документа с помощью History API.

Открываю эту страницу, которая лежит по пути PathA

```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Task 4.3</title>
    <script>
        function OnButtonClick(){
            window.history.pushState({"page": 1}, "My prev page", "https://hu.io/ui/task4.3/pathB/absent.html");
            window.history.back();
            let x = new XMLHttpRequest();
            x.open("GET", "https://hu.io/ui/task4.3/pathB/empty.html", false);
            x.send();
            alert(x.response);
        }
    </script>
</head>
<body>
    <form>
        <input type="button" value="Change History and Make Request" onclick="OnButtonClick();">
    </form>
</body>
</html>
```
При нажатии на кнопку меняется история на путь PathB и отправляется запрос на получение страницы, расположенной по пути PathB

- [ ] Отправить запрос на сервер. Посмотреть для какого пути прилетают куки.

Насколько я понимаю, поскольку я делаю запрос на путь PathB, вне зависимости от манипуляций с историей для такого запроса на сервер всё равно отправляется кука, записанная для пути PathB:

![](https://i.imgur.com/7qqXVZl.png)




## 5. Other

### 5.1 mixed content

- [ ] Сделать страницу, которая включает как HTTPS ресурсы, так и HTTP.
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Task 5.1</title>
</head>
<body>
    <img src="http://www.google.ru/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"/>
    <br>
    <img src="https://ost1.gismeteo.ru/assets/flat-ui/img/map-660.jpg"/>
</body>
</html>
```
- [ ] Посмотреть ошибки в консоли.
![](https://i.imgur.com/ue2KHS0.png)


### 5.2 nosniff

- [ ] Сделать страницу, которая включает в качестве скрипта текстовый файл с расширением `.txt`.
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Task 5.2</title>
    <script src="task5.2_script.txt"></script>
</head>
<body>

</body>
</html>
```
- [ ] Посмотреть, выполнится ли скрипт.
Да, скрипт выполнился:
![](https://i.imgur.com/EFXtEA6.png)
В текстовом файле было:
```
alert("This message is shown from the text file");
```
- [ ] Добавить в ответ сервера исходной страницы `X-Content-Type-Options: nosniff`.

Оказалось, что данный заголовок должен выставляться в ответ на запрос файла скрипта, а не файла страницы

```
@collect_handler
def no_sniff(request: Request, response: Response):
    if str(request.url).endswith("a.hu.io/ui/task5.2_script.txt"):
        return {"X-Content-Type-Options": "nosniff"}
    else:
        return {}

```

- [ ] Посмотреть, выполнится ли скрипт. Посмотреть на ошибки в консоли.
![](https://i.imgur.com/R8vY2RX.png)


### 5.3 referer

- [ ] Сделать `Same-Origin` и `Cross-Origin` запросы со страницы. Посмотреть в каком виде летит `Referer`.

- [ ] Запретить отправку `Referer` с этой страницы через `Referer-Policy`. Проверить на запросах из первого пункта.


### 5.4 spying service worker

- [ ] Установить шпионящего `Service Worker` на поддомен `a.hu.io`, который отправляет пути и параметры запросов на домен `b.hu.io`.
