├── stealth
├── vendor
│ ├── echos.json
│ ├── peers.json
│ ├── tasks.json
│ ├── account.json
│ ├── sessions.json
│ ├── internet.json
│ ├── interface.json
│ ├── redirects.json
│ ├── README.md
│ ├── hosts.json
│ ├── beacons.json
│ └── policies.json
├── .gitignore
├── source
│ ├── server
│ │ ├── Defender.gz
│ │ └── service
│ │ │ └── Blocker.mjs
│ ├── client
│ │ └── service
│ │ │ ├── Blocker.mjs
│ │ │ ├── Settings.mjs
│ │ │ ├── Echo.mjs
│ │ │ ├── Mode.mjs
│ │ │ ├── Task.mjs
│ │ │ ├── Beacon.mjs
│ │ │ ├── Policy.mjs
│ │ │ ├── Redirect.mjs
│ │ │ ├── Host.mjs
│ │ │ └── Cache.mjs
│ ├── optimizer
│ │ ├── HTML.mjs
│ │ └── CSS.mjs
│ └── parser
│ │ └── CSS.mjs
├── package
│ └── archlinux
│ │ ├── tholian-stealth.service
│ │ └── PKGBUILD
├── .eslintrc.json
├── README.md
└── review
│ ├── client
│ └── service
│ │ ├── Blocker.mjs
│ │ ├── Mode.mjs
│ │ └── Beacon.mjs
│ ├── connection
│ └── WHOIS.mjs
│ └── server
│ └── service
│ └── Blocker.mjs
├── base
├── .gitignore
├── package.json
├── source
│ ├── node
│ │ └── Buffer.mjs
│ ├── Map.mjs
│ ├── Set.mjs
│ ├── Date.mjs
│ ├── Number.mjs
│ ├── RegExp.mjs
│ ├── String.mjs
│ ├── Boolean.mjs
│ ├── Function.mjs
│ ├── MODULE.mjs
│ ├── Object.mjs
│ └── Array.mjs
├── review
│ ├── index.mjs
│ ├── Set.mjs
│ ├── Number.mjs
│ ├── RegExp.mjs
│ ├── String.mjs
│ ├── Date.mjs
│ ├── Map.mjs
│ ├── Function.mjs
│ ├── MODULE.mjs
│ ├── Boolean.mjs
│ ├── Object.mjs
│ └── Array.mjs
├── index.mjs
├── .eslintrc.json
└── README.md
├── covert
├── .gitignore
├── review
│ ├── index.mjs
│ └── MODULE.mjs
├── .eslintrc.json
├── index.mjs
└── README.md
├── .github
├── FUNDING.yml
└── workflows
│ ├── covert-scan.mjs
│ ├── covert-check.mjs
│ ├── eslint-lint.yml
│ ├── covert-check.yml
│ └── covert-scan.yml
├── browser
├── design
│ ├── common
│ │ ├── icon.woff
│ │ ├── tholian.ico
│ │ ├── vera-mono.woff
│ │ ├── museo-medium.woff
│ │ ├── museo-regular.woff
│ │ ├── tholian-android.png
│ │ ├── tholian-apple.png
│ │ ├── crystalline-bold.woff
│ │ ├── crystalline-light.woff
│ │ ├── crystalline-medium.woff
│ │ ├── crystalline-regular.woff
│ │ ├── theme-dark-element-select-default.svg
│ │ ├── theme-dark-element-select-focus.svg
│ │ ├── theme-light-element-select-default.svg
│ │ ├── theme-light-element-select-focus.svg
│ │ ├── tholian.svg
│ │ ├── index.css
│ │ ├── layout.css
│ │ └── theme.css
│ ├── appbar
│ │ ├── Mode.click.wav
│ │ ├── Address.blur.wav
│ │ ├── History.back.wav
│ │ ├── History.next.wav
│ │ ├── History.open.wav
│ │ ├── Address.focus.wav
│ │ ├── History.reload.wav
│ │ ├── Settings.hide.wav
│ │ ├── Settings.show.wav
│ │ ├── Address.navigate.wav
│ │ ├── Settings.browser.wav
│ │ ├── History.css
│ │ ├── Settings.css
│ │ ├── Mode.css
│ │ ├── Splitter.css
│ │ └── Splitter.mjs
│ ├── menu
│ │ ├── Context.hide.wav
│ │ ├── Context.show.wav
│ │ └── Context.select.wav
│ ├── backdrop
│ │ ├── Tabs.close.wav
│ │ ├── Tabs.select.wav
│ │ └── Webview.css
│ ├── sheet
│ │ ├── Console.hide.wav
│ │ ├── Console.show.wav
│ │ ├── Site.css
│ │ ├── Session.css
│ │ ├── Site.mjs
│ │ ├── Console.css
│ │ ├── Session.mjs
│ │ └── Console.mjs
│ ├── widget
│ │ ├── Image.css
│ │ ├── Audio.css
│ │ └── Video.css
│ ├── Assistant.mjs
│ ├── card
│ │ ├── Blocker.mjs
│ │ ├── Blocker.css
│ │ ├── Host.css
│ │ └── Session.css
│ └── index.css
├── package
│ ├── electron
│ │ ├── build
│ │ │ └── icon.png
│ │ ├── app
│ │ │ └── package.json
│ │ └── electron-builder.json
│ └── archlinux
│ │ ├── tholian-browser.desktop
│ │ ├── tholian-browser.svg
│ │ └── PKGBUILD
├── .gitignore
├── app
│ └── package.json
├── index.webmanifest
├── index.mjs
├── internal
│ ├── media.html
│ ├── search.html
│ ├── media.mjs
│ ├── debug.html
│ ├── search.mjs
│ ├── settings.html
│ ├── debug.mjs
│ ├── settings.mjs
│ ├── media
│ │ └── Media.css
│ ├── fix-host.html
│ ├── fix-connection.html
│ └── debug
│ │ └── Browser.css
├── .eslintrc.json
├── README.md
└── source
│ ├── ENVIRONMENT.mjs
│ └── Session.mjs
├── .gitignore
├── Dockerfile
├── package.json
├── guide
├── security
│ └── Network.md
├── README.md
└── concept
│ └── Service.md
└── TODO.md
/stealth/vendor/echos.json:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/stealth/vendor/peers.json:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/stealth/vendor/tasks.json:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/base/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
3 |
--------------------------------------------------------------------------------
/stealth/vendor/account.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/stealth/vendor/sessions.json:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/covert/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # shared with /base
3 | /extern/base.mjs
4 |
5 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: cookiengineer
4 |
5 |
--------------------------------------------------------------------------------
/browser/design/common/icon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/common/icon.woff
--------------------------------------------------------------------------------
/stealth/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # packages
3 | /build/*
4 |
5 | # shared with /base
6 | /extern/base.mjs
7 |
8 |
--------------------------------------------------------------------------------
/browser/design/common/tholian.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/common/tholian.ico
--------------------------------------------------------------------------------
/stealth/source/server/Defender.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/stealth/source/server/Defender.gz
--------------------------------------------------------------------------------
/browser/design/appbar/Mode.click.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/appbar/Mode.click.wav
--------------------------------------------------------------------------------
/browser/design/common/vera-mono.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/common/vera-mono.woff
--------------------------------------------------------------------------------
/browser/design/menu/Context.hide.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/menu/Context.hide.wav
--------------------------------------------------------------------------------
/browser/design/menu/Context.show.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/menu/Context.show.wav
--------------------------------------------------------------------------------
/browser/design/appbar/Address.blur.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/appbar/Address.blur.wav
--------------------------------------------------------------------------------
/browser/design/appbar/History.back.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/appbar/History.back.wav
--------------------------------------------------------------------------------
/browser/design/appbar/History.next.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/appbar/History.next.wav
--------------------------------------------------------------------------------
/browser/design/appbar/History.open.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/appbar/History.open.wav
--------------------------------------------------------------------------------
/browser/design/backdrop/Tabs.close.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/backdrop/Tabs.close.wav
--------------------------------------------------------------------------------
/browser/design/menu/Context.select.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/menu/Context.select.wav
--------------------------------------------------------------------------------
/browser/design/sheet/Console.hide.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/sheet/Console.hide.wav
--------------------------------------------------------------------------------
/browser/design/sheet/Console.show.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/sheet/Console.show.wav
--------------------------------------------------------------------------------
/stealth/vendor/internet.json:
--------------------------------------------------------------------------------
1 | {
2 | "connection": "mobile",
3 | "history": "stealth",
4 | "useragent": "stealth"
5 | }
--------------------------------------------------------------------------------
/browser/design/appbar/Address.focus.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/appbar/Address.focus.wav
--------------------------------------------------------------------------------
/browser/design/appbar/History.reload.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/appbar/History.reload.wav
--------------------------------------------------------------------------------
/browser/design/appbar/Settings.hide.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/appbar/Settings.hide.wav
--------------------------------------------------------------------------------
/browser/design/appbar/Settings.show.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/appbar/Settings.show.wav
--------------------------------------------------------------------------------
/browser/design/backdrop/Tabs.select.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/backdrop/Tabs.select.wav
--------------------------------------------------------------------------------
/browser/design/common/museo-medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/common/museo-medium.woff
--------------------------------------------------------------------------------
/browser/design/common/museo-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/common/museo-regular.woff
--------------------------------------------------------------------------------
/browser/design/common/tholian-android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/common/tholian-android.png
--------------------------------------------------------------------------------
/browser/design/common/tholian-apple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/common/tholian-apple.png
--------------------------------------------------------------------------------
/browser/package/electron/build/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/package/electron/build/icon.png
--------------------------------------------------------------------------------
/browser/design/appbar/Address.navigate.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/appbar/Address.navigate.wav
--------------------------------------------------------------------------------
/browser/design/appbar/Settings.browser.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/appbar/Settings.browser.wav
--------------------------------------------------------------------------------
/browser/design/common/crystalline-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/common/crystalline-bold.woff
--------------------------------------------------------------------------------
/browser/design/common/crystalline-light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/common/crystalline-light.woff
--------------------------------------------------------------------------------
/browser/design/common/crystalline-medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/common/crystalline-medium.woff
--------------------------------------------------------------------------------
/browser/design/common/crystalline-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2Cmo/stealth/X0/browser/design/common/crystalline-regular.woff
--------------------------------------------------------------------------------
/stealth/vendor/interface.json:
--------------------------------------------------------------------------------
1 | {
2 | "assistant": false,
3 | "theme": "dark",
4 | "enforce": false,
5 | "opentab": "stealth:blank"
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | /.github/ISSUES
3 | /.github/TOKEN
4 | /.github/workflows/*.report
5 |
6 |
7 | /node_modules
8 | /package-lock.json
9 |
10 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-alpine
2 | RUN mkdir "/profile"
3 | RUN node "./make.mjs"
4 | EXPOSE 65432
5 | CMD [ "node", "./stealth/stealth.mjs", "serve", "--profile=/profile" ]
6 |
--------------------------------------------------------------------------------
/base/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "base",
3 | "main": "./build/node.mjs",
4 | "module": "./build/node.mjs",
5 | "browser": "./build/browser.mjs",
6 | "scripts": {
7 | "postinstall": "node ./make.mjs"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/browser/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # packages
3 | /build/*
4 |
5 | # shared with /base
6 | /extern/base.mjs
7 |
8 | # shared with /stealth
9 | /source/Browser.mjs
10 | /source/Tab.mjs
11 | /source/client
12 | /source/parser
13 |
14 |
--------------------------------------------------------------------------------
/stealth/vendor/redirects.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "domain": "tholian.network",
4 | "redirects": [
5 | {
6 | "path": "/",
7 | "query": null,
8 | "location": "https://tholian.network/index.html"
9 | }
10 | ]
11 | }
12 | ]
13 |
--------------------------------------------------------------------------------
/base/source/node/Buffer.mjs:
--------------------------------------------------------------------------------
1 |
2 | export const Buffer = (function(global) {
3 | return global.Buffer;
4 | })(typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this));
5 |
6 | export const isBuffer = Buffer.isBuffer;
7 |
8 |
--------------------------------------------------------------------------------
/stealth/package/archlinux/tholian-stealth.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Tholian Stealth
3 | After=network.target
4 |
5 | [Service]
6 | ExecStart=/usr/bin/tholian-stealth serve
7 | Restart=always
8 | RestartSec=10
9 |
10 | [Install]
11 | WantedBy=default.target
12 |
13 |
--------------------------------------------------------------------------------
/browser/package/archlinux/tholian-browser.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Tholian Browser
3 | Name[en_US]=Tholian Browser
4 | GenericName=Stealth
5 | Exec=/usr/bin/tholian-browser
6 | Icon=tholian-browser
7 | Type=Application
8 | NoDisplay=false
9 | Categories=Network;
10 |
11 |
--------------------------------------------------------------------------------
/browser/design/common/theme-dark-element-select-default.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/browser/design/common/theme-dark-element-select-focus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/browser/design/common/theme-light-element-select-default.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/browser/design/common/theme-light-element-select-focus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/stealth/vendor/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Stealth Vendor Profile
3 |
4 | This Stealth Vendor Profile is shipped with its default settings
5 | for all Browser instances in the Tholian Network.
6 |
7 | The Profile's default settngs are generated via the [stealth-vendor](https://github.com/tholian-network/stealth-vendor) Repository.
8 |
9 |
--------------------------------------------------------------------------------
/base/source/Map.mjs:
--------------------------------------------------------------------------------
1 |
2 | export const Map = (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)).Map;
3 |
4 | if (typeof Map.isMap !== 'function') {
5 |
6 | Map.isMap = function(map) {
7 | return Object.prototype.toString.call(map) === '[object Map]';
8 | };
9 |
10 | }
11 |
12 | export const isMap = Map.isMap;
13 |
14 |
--------------------------------------------------------------------------------
/base/source/Set.mjs:
--------------------------------------------------------------------------------
1 |
2 | export const Set = (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)).Set;
3 |
4 | if (typeof Set.isSet !== 'function') {
5 |
6 | Set.isSet = function(set) {
7 | return Object.prototype.toString.call(set) === '[object Set]';
8 | };
9 |
10 | }
11 |
12 | export const isSet = Set.isSet;
13 |
14 |
--------------------------------------------------------------------------------
/base/source/Date.mjs:
--------------------------------------------------------------------------------
1 |
2 | export const Date = (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)).Date;
3 |
4 | if (typeof Date.isDate !== 'function') {
5 |
6 | Date.isDate = function(dat) {
7 | return Object.prototype.toString.call(dat) === '[object Date]';
8 | };
9 |
10 | }
11 |
12 | export const isDate = Date.isDate;
13 |
14 |
--------------------------------------------------------------------------------
/base/source/Number.mjs:
--------------------------------------------------------------------------------
1 |
2 | export const Number = (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)).Number;
3 |
4 | if (typeof Number.isNumber !== 'function') {
5 |
6 | Number.isNumber = function(num) {
7 | return Object.prototype.toString.call(num) === '[object Number]';
8 | };
9 |
10 | }
11 |
12 | export const isNumber = Number.isNumber;
13 |
14 |
--------------------------------------------------------------------------------
/base/source/RegExp.mjs:
--------------------------------------------------------------------------------
1 |
2 | export const RegExp = (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)).RegExp;
3 |
4 | if (typeof RegExp.isRegExp !== 'function') {
5 |
6 | RegExp.isRegExp = function(reg) {
7 | return Object.prototype.toString.call(reg) === '[object RegExp]';
8 | };
9 |
10 | }
11 |
12 | export const isRegExp = RegExp.isRegExp;
13 |
14 |
--------------------------------------------------------------------------------
/base/source/String.mjs:
--------------------------------------------------------------------------------
1 |
2 | export const String = (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)).String;
3 |
4 | if (typeof String.isString !== 'function') {
5 |
6 | String.isString = function(str) {
7 | return Object.prototype.toString.call(str) === '[object String]';
8 | };
9 |
10 | }
11 |
12 | export const isString = String.isString;
13 |
14 |
--------------------------------------------------------------------------------
/base/source/Boolean.mjs:
--------------------------------------------------------------------------------
1 |
2 | export const Boolean = (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)).Boolean;
3 |
4 | if (typeof Boolean.isBoolean !== 'function') {
5 |
6 | Boolean.isBoolean = function(bol) {
7 | return Object.prototype.toString.call(bol) === '[object Boolean]';
8 | };
9 |
10 | }
11 |
12 | export const isBoolean = Boolean.isBoolean;
13 |
14 |
--------------------------------------------------------------------------------
/base/source/Function.mjs:
--------------------------------------------------------------------------------
1 |
2 | export const Function = (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)).Function;
3 |
4 | if (typeof Function.isFunction !== 'function') {
5 |
6 | Function.isFunction = function(fun) {
7 | return Object.prototype.toString.call(fun) === '[object Function]';
8 | };
9 |
10 | }
11 |
12 | export const isFunction = Function.isFunction;
13 |
14 |
--------------------------------------------------------------------------------
/browser/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tholian-browser",
3 | "version": "X0:2021-11-11",
4 | "description": "Secure, Peer-to-Peer, Private and Automateable Web Browser/Scraper/Proxy",
5 | "homepage": "https://tholian.network/index.html",
6 | "repository": "https://github.com/tholian-network/stealth",
7 | "main": "index.js",
8 | "author": {
9 | "name": "Tholian Network",
10 | "email": "void@tholian.network"
11 | }
12 | }
--------------------------------------------------------------------------------
/browser/index.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "dir": "ltr",
3 | "lang": "en-us",
4 | "short_name": "Stealth",
5 | "name": "Tholian Stealth",
6 | "display": "fullscreen",
7 | "start_url": "/browser/index.html",
8 | "background_color": "#000000",
9 | "theme_color": "#000000",
10 |
11 | "icons": [{
12 | "src": "/browser/design/common/tholian-android.png",
13 | "sizes": "256x256",
14 | "type": "image/png"
15 | }]
16 | }
17 |
--------------------------------------------------------------------------------
/browser/package/electron/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tholian-browser",
3 | "version": "X0:2021-11-11",
4 | "description": "Secure, Peer-to-Peer, Private and Automateable Web Browser/Scraper/Proxy",
5 | "homepage": "https://tholian.network/index.html",
6 | "repository": "https://github.com/tholian-network/stealth",
7 | "main": "index.js",
8 | "author": {
9 | "name": "Tholian Network",
10 | "email": "void@tholian.network"
11 | },
12 | "devDependencies": {
13 | "electron": "19.0.8"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/browser/design/common/tholian.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/browser/design/backdrop/Webview.css:
--------------------------------------------------------------------------------
1 |
2 | browser-backdrop-webview {
3 | display: block;
4 | position: fixed;
5 | width: auto;
6 | height: auto;
7 | top: 0px;
8 | right: 0px;
9 | bottom: 0px;
10 | left: 0px;
11 | margin: 0px;
12 | padding: 0px;
13 | transition: 200ms all ease-out;
14 | user-select: none;
15 | -webkit-user-select: none;
16 | pointer-events: auto;
17 | }
18 |
19 | browser-backdrop-webview iframe {
20 | display: block;
21 | width: 100%;
22 | height: 100%;
23 | border: 0px solid transparent;
24 | box-sizing: border-box;
25 | -webkit-overflow-scrolling: touch;
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/browser/design/sheet/Site.css:
--------------------------------------------------------------------------------
1 |
2 | browser-sheet-site {
3 | display: block;
4 | position: fixed;
5 | width: auto;
6 | height: auto;
7 | top: 40px;
8 | right: 0px;
9 | bottom: 0px;
10 | left: 0px;
11 | margin: 0px;
12 | padding: 0px;
13 | color: var(--layout-default-color);
14 | font-family: 'museo';
15 | background: var(--layout-default-background);
16 | border: 0px solid transparent;
17 | transform: translate(100%, 0%);
18 | transition: 200ms transform ease-out;
19 | overflow-x: hidden;
20 | overflow-y: auto;
21 | }
22 |
23 | browser-sheet-site.active {
24 | transform: translate(0%, 0%);
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/browser/design/sheet/Session.css:
--------------------------------------------------------------------------------
1 |
2 | browser-sheet-session {
3 | display: block;
4 | position: fixed;
5 | width: auto;
6 | height: auto;
7 | top: 40px;
8 | right: 0px;
9 | bottom: 0px;
10 | left: 0px;
11 | margin: 0px;
12 | padding: 0px;
13 | color: var(--layout-default-color);
14 | font-family: 'museo';
15 | background: var(--layout-default-background);
16 | border: 0px solid transparent;
17 | transform: translate(100%, 0%);
18 | transition: 200ms transform ease-out;
19 | overflow-x: hidden;
20 | overflow-y: auto;
21 | }
22 |
23 | browser-sheet-session.active {
24 | transform: translate(0%, 0%);
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/browser/index.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Browser, isBrowser } from './source/Browser.mjs';
3 | import { Client, isClient } from './source/Client.mjs';
4 | import { ENVIRONMENT } from './source/ENVIRONMENT.mjs';
5 | import { Session, isSession } from './source/Session.mjs';
6 | import { Tab, isTab } from './source/Tab.mjs';
7 |
8 |
9 |
10 | export default {
11 |
12 | isBrowser: isBrowser,
13 | isClient: isClient,
14 | isSession: isSession,
15 | isTab: isTab,
16 |
17 | Browser: Browser,
18 | Client: Client,
19 | ENVIRONMENT: ENVIRONMENT,
20 | Session: Session,
21 | Tab: Tab
22 |
23 | };
24 |
25 |
--------------------------------------------------------------------------------
/browser/package/archlinux/tholian-browser.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/browser/internal/media.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | stealth:media
6 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/browser/internal/search.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | stealth:search
6 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/covert/review/index.mjs:
--------------------------------------------------------------------------------
1 |
2 | // import Covert from './Covert.mjs';
3 | // import Filesystem from './Filesystem.mjs';
4 | import MODULE from './MODULE.mjs';
5 | // import Network from './Network.mjs';
6 | // import Renderer from './Renderer.mjs';
7 | import Results from './Results.mjs';
8 | import Review from './Review.mjs';
9 | import Timeline from './Timeline.mjs';
10 |
11 |
12 |
13 | export const REVIEWS = [
14 |
15 | MODULE,
16 |
17 | // Review and Benchmark
18 | Review,
19 | Results,
20 | Timeline,
21 |
22 | // Covert
23 | // Covert,
24 | // Filesystem,
25 | // Network, // Cannot be tested
26 | // Renderer
27 |
28 | ];
29 |
30 | export const SOURCES = {
31 |
32 | 'covert/ENVIRONMENT': null
33 |
34 | };
35 |
36 |
--------------------------------------------------------------------------------
/browser/package/electron/electron-builder.json:
--------------------------------------------------------------------------------
1 | {
2 | "appId": "tholian-browser",
3 | "asar": true,
4 | "asarUnpack": [
5 | "browser/**/*",
6 | "stealth/**/*"
7 | ],
8 | "artifactName": "${name}-${version}-${os}-${arch}.${ext}",
9 | "directories": {
10 | "app": "app",
11 | "buildResources": "build",
12 | "output": "dist"
13 | },
14 | "productName": "Tholian Browser",
15 | "copyright": "Copyright (2021) Tholian Network",
16 | "linux": {
17 | "category": "Network",
18 | "target": [
19 | "appimage",
20 | "zip"
21 | ]
22 | },
23 | "mac": {
24 | "category": "public.app-category.productivity",
25 | "darkModeSupport": true,
26 | "target": [
27 | "zip"
28 | ]
29 | },
30 | "win": {
31 | "target": [
32 | "zip"
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/browser/internal/media.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Element } from '../design/Element.mjs';
3 | import { Media } from '../internal/media/Media.mjs';
4 | import { ENVIRONMENT } from '../source/ENVIRONMENT.mjs';
5 | import { URL } from '../source/parser/URL.mjs';
6 |
7 |
8 |
9 | let browser = window.parent.BROWSER || null;
10 | if (browser !== null) {
11 |
12 | let widget = Media.from({ source: null }, [ 'refresh', 'fullscreen', 'download' ]);
13 | if (widget !== null) {
14 |
15 | let body = Element.query('body');
16 | if (body !== null) {
17 | widget.render(body);
18 | }
19 |
20 |
21 | if (URL.isURL(ENVIRONMENT.flags.url) === true) {
22 |
23 | widget.value({
24 | source: ENVIRONMENT.flags.url
25 | });
26 |
27 | }
28 |
29 | }
30 |
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/.github/workflows/covert-scan.mjs:
--------------------------------------------------------------------------------
1 |
2 | import fs from 'fs';
3 | import process from 'process';
4 |
5 |
6 | let REPORT = Array.from(process.argv).slice(2).find((v) => v.startsWith('.github')) || null;
7 | if (REPORT !== null) {
8 |
9 | let buffer = fs.readFileSync(REPORT, 'utf8');
10 | if (buffer !== null) {
11 |
12 | let failed = false;
13 |
14 | buffer.split('\n').forEach((line) => {
15 |
16 | if (
17 | line.startsWith('(!)')
18 | && line.trim() !== '(!)'
19 | && line.includes('>') === false
20 | ) {
21 | console.log(line);
22 | failed = true;
23 | }
24 |
25 | });
26 |
27 | if (failed === true) {
28 | process.exit(1);
29 | } else {
30 | process.exit(0);
31 | }
32 |
33 | }
34 |
35 | } else {
36 | process.exit(1);
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/base/source/MODULE.mjs:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 |
4 | console: console,
5 |
6 | Array: Array,
7 | Boolean: Boolean,
8 | Buffer: Buffer,
9 | Date: Date,
10 | Emitter: Emitter,
11 | Function: Function,
12 | Map: Map,
13 | Number: Number,
14 | Object: Object,
15 | RegExp: RegExp,
16 | Set: Set,
17 | String: String,
18 |
19 | isArray: Array.isArray,
20 | isBoolean: Boolean.isBoolean,
21 | isBuffer: Buffer.isBuffer,
22 | isDate: Date.isDate,
23 | isEmitter: Emitter.isEmitter,
24 | isFunction: Function.isFunction,
25 | isMap: Map.isMap,
26 | isNumber: Number.isNumber,
27 | isObject: Object.isObject,
28 | isRegExp: RegExp.isRegExp,
29 | isSet: Set.isSet,
30 | isString: String.isString
31 |
32 | };
33 |
34 |
--------------------------------------------------------------------------------
/browser/internal/debug.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | stealth:debug
6 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.github/workflows/covert-check.mjs:
--------------------------------------------------------------------------------
1 |
2 | import fs from 'fs';
3 | import process from 'process';
4 |
5 |
6 | let REPORT = Array.from(process.argv).slice(2).find((v) => v.startsWith('.github')) || null;
7 | if (REPORT !== null) {
8 |
9 | let buffer = fs.readFileSync(REPORT, 'utf8');
10 | if (buffer !== null) {
11 |
12 | let failed = false;
13 |
14 | buffer.split('\n').forEach((line) => {
15 |
16 | if (line.includes('>') === false) {
17 |
18 | if (line.startsWith('(!)') && line.trim() !== '(!)') {
19 | console.log(line);
20 | failed = true;
21 | } else if (line.startsWith('(?)') && line.trim() !== '(?)') {
22 | console.log(line);
23 | failed = true;
24 | }
25 |
26 | }
27 |
28 | });
29 |
30 | if (failed === true) {
31 | process.exit(1);
32 | } else {
33 | process.exit(0);
34 | }
35 |
36 | }
37 |
38 | } else {
39 | process.exit(1);
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/.github/workflows/eslint-lint.yml:
--------------------------------------------------------------------------------
1 |
2 | name: ESLint Lint
3 |
4 | on: [push]
5 |
6 | jobs:
7 |
8 | lint-base:
9 |
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: actions/setup-node@v1
15 | with:
16 | node-version: 14
17 | - run: npm install
18 | - run: npx eslint ./base
19 |
20 | lint-covert:
21 |
22 | runs-on: ubuntu-latest
23 |
24 | steps:
25 | - uses: actions/checkout@v2
26 | - uses: actions/setup-node@v1
27 | with:
28 | node-version: 14
29 | - run: npm install
30 | - run: npx eslint ./covert
31 |
32 | lint-stealth:
33 |
34 | runs-on: ubuntu-latest
35 |
36 | steps:
37 | - uses: actions/checkout@v2
38 | - uses: actions/setup-node@v1
39 | with:
40 | node-version: 14
41 | - run: npm install
42 | - run: npx eslint ./stealth
43 |
44 |
--------------------------------------------------------------------------------
/stealth/vendor/hosts.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "domain": "tholian.network",
4 | "hosts": [
5 | {
6 | "ip": "185.199.108.153",
7 | "scope": "public",
8 | "type": "v4"
9 | }, {
10 | "ip": "185.199.109.153",
11 | "scope": "public",
12 | "type": "v4"
13 | }, {
14 | "ip": "185.199.110.153",
15 | "scope": "public",
16 | "type": "v4"
17 | }, {
18 | "ip": "185.199.111.153",
19 | "scope": "public",
20 | "type": "v4"
21 | }
22 | ]
23 | }, {
24 | "domain": "searx.prvcy.eu",
25 | "hosts": [
26 | {
27 | "ip": "95.217.14.39",
28 | "scope": "public",
29 | "type": "v4"
30 | },
31 | {
32 | "ip": "2a01:4f9:c010:5df1::1",
33 | "scope": "public",
34 | "type": "v6"
35 | }
36 | ]
37 | }, {
38 | "domain": "wiby.me",
39 | "hosts": [
40 | {
41 | "ip": "172.93.49.59",
42 | "scope": "public",
43 | "type": "v4"
44 | }
45 | ]
46 | }
47 | ]
48 |
--------------------------------------------------------------------------------
/browser/internal/search.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { isString } from '../extern/base.mjs';
3 | import { Element } from '../design/Element.mjs';
4 | import { Search } from '../internal/search/Search.mjs';
5 | import { ENVIRONMENT } from '../source/ENVIRONMENT.mjs';
6 |
7 |
8 |
9 | let browser = window.parent.BROWSER || null;
10 | if (browser !== null) {
11 |
12 | let widget = Search.from({ keywords: '', results: [] }, [ 'search' ]);
13 | if (widget !== null) {
14 |
15 | let body = Element.query('body');
16 | if (body !== null) {
17 | widget.render(body);
18 | }
19 |
20 |
21 | if (isString(ENVIRONMENT.flags.keywords) === true) {
22 |
23 | let keywords = decodeURIComponent(ENVIRONMENT.flags.keywords).trim();
24 | if (keywords.length > 3) {
25 |
26 | widget.value({
27 | keywords: keywords,
28 | results: []
29 | });
30 |
31 | widget.buttons.search.emit('click');
32 |
33 | }
34 |
35 | }
36 |
37 | }
38 |
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/stealth/vendor/beacons.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "domain": "tholian.network",
4 | "beacons": [
5 | {
6 | "path": "/*",
7 | "query": null,
8 | "select": "article div,article figure,article p,article ul",
9 | "term": "article"
10 | },
11 | {
12 | "path": "/*",
13 | "query": null,
14 | "select": "meta[property=\"og:description\"]",
15 | "term": "description"
16 | },
17 | {
18 | "path": "/*",
19 | "query": null,
20 | "select": "meta[property=\"og:locale\"]",
21 | "term": "language"
22 | },
23 | {
24 | "path": "/*",
25 | "query": null,
26 | "select": "footer",
27 | "term": "publisher"
28 | },
29 | {
30 | "path": "/*",
31 | "query": null,
32 | "select": "meta[property=\"og:title\"]",
33 | "term": "title"
34 | },
35 | {
36 | "path": "/*",
37 | "query": null,
38 | "select": "meta[property=\"og:type\"]",
39 | "term": "type"
40 | }
41 | ]
42 | }
43 | ]
44 |
--------------------------------------------------------------------------------
/browser/design/sheet/Site.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Element } from '../Element.mjs';
3 | import { Widget } from '../Widget.mjs';
4 |
5 |
6 |
7 | const update = function(/* tab, tabs */) {
8 | // TODO: Update according to tab meta info
9 | };
10 |
11 |
12 | const Site = function(browser) {
13 |
14 | this.element = new Element('browser-sheet-site', [
15 | '',
16 | '\tSite Modes ',
17 | ' ',
18 | '',
19 | '\tSite Beacons ',
20 | ' ',
21 | '',
22 | '\tSite Echoes ',
23 | ' '
24 | ].join(''));
25 |
26 |
27 | this.element.on('contextmenu', (e) => {
28 | e.preventDefault();
29 | e.stopPropagation();
30 | });
31 |
32 | this.element.on('show', () => {
33 | this.element.state('active');
34 | });
35 |
36 | this.element.on('hide', () => {
37 | this.element.state('');
38 | });
39 |
40 |
41 | browser.on('show', (tab) => update.call(this, tab));
42 | browser.on('refresh', (tab) => update.call(this, tab));
43 |
44 |
45 | Widget.call(this);
46 |
47 | };
48 |
49 |
50 | Site.prototype = Object.assign({}, Widget.prototype);
51 |
52 |
53 | export { Site };
54 |
55 |
--------------------------------------------------------------------------------
/stealth/source/client/service/Blocker.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Emitter, isFunction, isObject } from '../../../extern/base.mjs';
3 |
4 |
5 |
6 | const Blocker = function(client) {
7 |
8 | this.client = client;
9 | Emitter.call(this);
10 |
11 | };
12 |
13 |
14 | Blocker.prototype = Object.assign({}, Emitter.prototype, {
15 |
16 | toJSON: function() {
17 |
18 | let blob = Emitter.prototype.toJSON.call(this);
19 | let data = {
20 | events: blob.data.events,
21 | journal: blob.data.journal
22 | };
23 |
24 | return {
25 | 'type': 'Blocker Service',
26 | 'data': data
27 | };
28 |
29 | },
30 |
31 | read: function(payload, callback) {
32 |
33 | payload = isObject(payload) ? payload : null;
34 | callback = isFunction(callback) ? callback : null;
35 |
36 |
37 | if (payload !== null && callback !== null) {
38 |
39 | this.once('read', (response) => callback(response));
40 |
41 | this.client.send({
42 | headers: {
43 | service: 'blocker',
44 | method: 'read'
45 | },
46 | payload: payload
47 | });
48 |
49 | } else if (callback !== null) {
50 | callback(null);
51 | }
52 |
53 | }
54 |
55 | });
56 |
57 |
58 | export { Blocker };
59 |
60 |
--------------------------------------------------------------------------------
/browser/internal/settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | stealth:settings
6 |
30 |
31 |
32 |
33 | Browser Settings
34 |
35 | Here you can configure all Browser Settings that are stored
36 | in your Profile folder. On this machine, the Stealth Profile
37 | folder is located at:
38 |
39 |
40 | /home/$USER/Stealth
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/base/review/index.mjs:
--------------------------------------------------------------------------------
1 |
2 | import _console from './console.mjs';
3 | import _Array from './Array.mjs';
4 | import _Boolean from './Boolean.mjs';
5 | import _Buffer from './Buffer.mjs';
6 | import _Date from './Date.mjs';
7 | import Emitter from './Emitter.mjs';
8 | import _Function from './Function.mjs';
9 | import _Map from './Map.mjs';
10 | import MODULE from './MODULE.mjs';
11 | import _Number from './Number.mjs';
12 | import _Object from './Object.mjs';
13 | import _RegExp from './RegExp.mjs';
14 | import _Set from './Set.mjs';
15 | import _String from './String.mjs';
16 |
17 |
18 |
19 | export const REVIEWS = [
20 |
21 | MODULE,
22 |
23 | _console,
24 |
25 | _Array,
26 | _Boolean,
27 | _Buffer,
28 | _Date,
29 | Emitter,
30 | _Function,
31 | _Map,
32 | _Number,
33 | _Object,
34 | _RegExp,
35 | _Set,
36 | _String
37 |
38 | ];
39 |
40 | export const SOURCES = {
41 |
42 | // Map source.id to review.id
43 | 'base/browser/Buffer': 'base/Buffer',
44 | 'base/browser/console': 'base/console',
45 | 'base/node/Buffer': 'base/Buffer',
46 | 'base/node/console': 'base/console',
47 |
48 | // Map review.id to source.id
49 | 'base/Buffer': 'base/browser/Buffer',
50 | 'base/console': 'base/node/console'
51 |
52 | };
53 |
54 |
--------------------------------------------------------------------------------
/browser/design/sheet/Console.css:
--------------------------------------------------------------------------------
1 |
2 | base-console base-console-line {
3 | font-family: 'vera-mono' !important;
4 | }
5 |
6 | browser-sheet-console {
7 | position: fixed;
8 | top: auto;
9 | right: auto;
10 | bottom: 1px;
11 | left: 1px;
12 | }
13 |
14 | browser-sheet-console button {
15 | display: inline-block;
16 | min-width: 32px;
17 | margin: 0px;
18 | padding: 0px;
19 | line-height: 32px;
20 | height: 32px;
21 | color: var(--element-default-color);
22 | font-size: 24px;
23 | text-align: center;
24 | background: var(--layout-default-background);
25 | border: 1px solid transparent;
26 | border-radius: 4px;
27 | box-sizing: border-box;
28 | transition: 200ms all ease-out;
29 | vertical-align: middle;
30 | user-select: none;
31 | -webkit-user-select: none;
32 | appearance: none;
33 | -moz-appearance: none;
34 | -webkit-appearance: none;
35 | outline: none;
36 | cursor: pointer;
37 | }
38 |
39 | browser-sheet-console button:hover,
40 | browser-sheet-console button.active {
41 | color: var(--element-active-color);
42 | border: 1px solid var(--element-focus-color);
43 | }
44 |
45 | browser-sheet-console button:before {
46 | display: inline-block;
47 | width: 100%;
48 | font-family: 'icon';
49 | text-align: center;
50 | speak: none;
51 | -webkit-font-smoothing: antialiased;
52 | content: '\e1b0';
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/base/source/Object.mjs:
--------------------------------------------------------------------------------
1 |
2 | export const Object = (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)).Object;
3 |
4 | if (typeof Object.isObject !== 'function') {
5 |
6 | Object.isObject = function(obj) {
7 | return Object.prototype.toString.call(obj) === '[object Object]';
8 | };
9 |
10 | }
11 |
12 | if (typeof Object.clone !== 'function') {
13 |
14 | Object.clone = function(target) {
15 |
16 | target = target instanceof Object ? target : {};
17 |
18 |
19 | for (let a = 1, al = arguments.length; a < al; a++) {
20 |
21 | let source = arguments[a];
22 | if (source) {
23 |
24 | for (let prop in source) {
25 |
26 | if (Object.prototype.hasOwnProperty.call(source, prop) === true) {
27 |
28 | let other_value = source[prop];
29 | if (other_value instanceof Array) {
30 |
31 | target[prop] = [];
32 | Object.clone(target[prop], source[prop]);
33 |
34 | } else if (other_value instanceof Object) {
35 |
36 | target[prop] = {};
37 | Object.clone(target[prop], source[prop]);
38 |
39 | } else {
40 |
41 | target[prop] = source[prop];
42 |
43 | }
44 |
45 | }
46 |
47 | }
48 |
49 | }
50 |
51 | }
52 |
53 |
54 | return target;
55 |
56 | };
57 |
58 | }
59 |
60 | export const isObject = Object.isObject;
61 |
62 |
--------------------------------------------------------------------------------
/stealth/source/optimizer/HTML.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { isFunction, isObject } from '../../extern/base.mjs';
3 |
4 |
5 |
6 | const Optimizer = {
7 |
8 | check: function(ref, config, callback) {
9 |
10 | ref = isObject(ref) ? ref : null;
11 | config = isObject(config) ? config : null;
12 | callback = isFunction(callback) ? callback : null;
13 |
14 |
15 | if (ref !== null && config !== null && callback !== null) {
16 |
17 | let path = ref.path || null;
18 | if (path !== null && path.endsWith('.html')) {
19 | callback(true);
20 | } else {
21 | callback(false);
22 | }
23 |
24 | } else if (ref !== null && config !== null) {
25 |
26 | let path = ref.path || null;
27 | if (path !== null && path.endsWith('.html')) {
28 | return true;
29 | } else {
30 | return false;
31 | }
32 |
33 | } else if (callback !== null) {
34 | callback(false);
35 | } else {
36 | return false;
37 | }
38 |
39 | },
40 |
41 | optimize: function(ref, config, callback) {
42 |
43 | ref = isObject(ref) ? ref : null;
44 | config = isObject(config) ? config : null;
45 | callback = isFunction(callback) ? callback : null;
46 |
47 | }
48 |
49 | };
50 |
51 |
52 | export const check = Optimizer.check;
53 | export const optimize = Optimizer.optimize;
54 |
55 | export { Optimizer };
56 |
57 |
--------------------------------------------------------------------------------
/stealth/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": false,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": "eslint:recommended",
8 | "parser": "@babel/eslint-parser",
9 | "parserOptions": {
10 | "ecmaVersion": 2018,
11 | "sourceType": "module",
12 | "requireConfigFile": false
13 | },
14 | "overrides": [{
15 | "files": [ "*.mjs" ],
16 | "rules": {}
17 | }, {
18 | "files": [ "extern/base.mjs" ],
19 | "rules": {
20 | "no-control-regex": "off",
21 | "no-undef": "off"
22 | }
23 | }],
24 | "rules": {
25 | "no-restricted-globals": [
26 | "error",
27 | { "name": "__dirname" },
28 | { "name": "__filename" },
29 | { "name": "Buffer" },
30 | { "name": "console" },
31 | { "name": "exports" },
32 | { "name": "module" },
33 | { "name": "process" },
34 | { "name": "require" },
35 | { "name": "TextDecoder" },
36 | { "name": "TextEncoder" },
37 | { "name": "URL" },
38 | { "name": "URLSearchParams" }
39 | ],
40 | "arrow-parens": [
41 | "error",
42 | "always"
43 | ],
44 | "indent": [
45 | "error",
46 | "tab"
47 | ],
48 | "linebreak-style": [
49 | "error",
50 | "unix"
51 | ],
52 | "quotes": [
53 | "error",
54 | "single"
55 | ],
56 | "semi": [
57 | "error",
58 | "always"
59 | ]
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/stealth/package/archlinux/PKGBUILD:
--------------------------------------------------------------------------------
1 |
2 | pkgname=tholian-stealth
3 | pkgver=VERSION
4 | pkgrel=1
5 | pkgdesc="Secure, Peer-to-Peer, Private and Automateable Web Browser/Scraper/Proxy"
6 | arch=(any)
7 | url="https://tholian.network/index.html"
8 | license=("GPL")
9 | depends=("nodejs")
10 | optdepends=("systemd: System Daemon Integration")
11 | conflicts=("tholian-browser")
12 |
13 |
14 |
15 | package() {
16 |
17 | mkdir -p "$pkgdir/usr/bin";
18 | mkdir -p "$pkgdir/usr/lib/systemd/system";
19 | mkdir -p "$pkgdir/usr/lib/tholian/browser";
20 | mkdir -p "$pkgdir/usr/lib/tholian/stealth";
21 | mkdir -p "$pkgdir/usr/lib/tholian/stealth/vendor/cache/headers";
22 | mkdir -p "$pkgdir/usr/lib/tholian/stealth/vendor/cache/payload";
23 |
24 | cp -r $srcdir/browser/* "$pkgdir/usr/lib/tholian/browser/";
25 | find "$pkgdir/usr/lib/tholian/browser" -type d -exec chmod 755 {} \;
26 | find "$pkgdir/usr/lib/tholian/browser" -type f -exec chmod 644 {} \;
27 |
28 | cp -r $srcdir/stealth/* "$pkgdir/usr/lib/tholian/stealth/";
29 | find "$pkgdir/usr/lib/tholian/stealth" -type d -exec chmod 755 {} \;
30 | find "$pkgdir/usr/lib/tholian/stealth" -type f -exec chmod 644 {} \;
31 |
32 | install -Dm755 "$srcdir/tholian-stealth.mjs" "$pkgdir/usr/bin/tholian-stealth";
33 | install -Dm644 "$srcdir/tholian-stealth.service" "$pkgdir/usr/lib/systemd/system/tholian-stealth.service";
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/base/index.mjs:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | console,
4 | Array,
5 | Boolean,
6 | Buffer,
7 | Date,
8 | Emitter,
9 | Function,
10 | Map,
11 | Number,
12 | Object,
13 | RegExp,
14 | Set,
15 | String
16 | } from './build/node.mjs';
17 |
18 | export {
19 | console,
20 | Array, isArray,
21 | Boolean, isBoolean,
22 | Buffer, isBuffer,
23 | Date, isDate,
24 | Emitter, isEmitter,
25 | Function, isFunction,
26 | Map, isMap,
27 | Number, isNumber,
28 | Object, isObject,
29 | RegExp, isRegExp,
30 | Set, isSet,
31 | String, isString
32 | } from './build/node.mjs';
33 |
34 |
35 |
36 | export default {
37 |
38 | console: console,
39 |
40 | Array: Array,
41 | Boolean: Boolean,
42 | Buffer: Buffer,
43 | Date: Date,
44 | Emitter: Emitter,
45 | Function: Function,
46 | Map: Map,
47 | Number: Number,
48 | Object: Object,
49 | RegExp: RegExp,
50 | Set: Set,
51 | String: String,
52 |
53 | isArray: Array.isArray,
54 | isBoolean: Boolean.isBoolean,
55 | isBuffer: Buffer.isBuffer,
56 | isDate: Date.isDate,
57 | isEmitter: Emitter.isEmitter,
58 | isFunction: Function.isFunction,
59 | isMap: Map.isMap,
60 | isNumber: Number.isNumber,
61 | isObject: Object.isObject,
62 | isRegExp: RegExp.isRegExp,
63 | isSet: Set.isSet,
64 | isString: String.isString
65 |
66 | };
67 |
68 |
--------------------------------------------------------------------------------
/base/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": "eslint:recommended",
8 | "parser": "@babel/eslint-parser",
9 | "parserOptions": {
10 | "ecmaVersion": 2018,
11 | "sourceType": "module",
12 | "requireConfigFile": false
13 | },
14 | "overrides": [{
15 | "files": [ "*.mjs" ],
16 | "rules": {}
17 | }, {
18 | "files": [ "build/browser.mjs" ],
19 | "rules": {
20 | "no-undef": "off"
21 | }
22 | }, {
23 | "files": [ "build/node.mjs" ],
24 | "globals": {
25 | "global": true,
26 | "window": true
27 | },
28 | "rules": {
29 | "no-control-regex": "off",
30 | "no-undef": "off"
31 | }
32 | }, {
33 | "files": [ "source/node/console.mjs" ],
34 | "rules": { "no-control-regex": "off" }
35 | }, {
36 | "files": [ "source/MODULE.mjs" ],
37 | "globals": {
38 | "console": true,
39 | "Buffer": true,
40 | "Emitter": true
41 | }
42 | }],
43 | "rules": {
44 | "arrow-parens": [
45 | "error",
46 | "always"
47 | ],
48 | "indent": [
49 | "error",
50 | "tab"
51 | ],
52 | "linebreak-style": [
53 | "error",
54 | "unix"
55 | ],
56 | "no-unused-vars": [
57 | "error", {
58 | "argsIgnorePattern": "^(assert)$"
59 | }
60 | ],
61 | "quotes": [
62 | "error",
63 | "single"
64 | ],
65 | "semi": [
66 | "error",
67 | "always"
68 | ]
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/browser/design/widget/Image.css:
--------------------------------------------------------------------------------
1 |
2 | browser-widget-image {
3 | all: unset;
4 | display: block;
5 | position: relative;
6 | width: 800px;
7 | margin: 0px;
8 | padding: 16px;
9 | background: var(--surface-default-background);
10 | border: 1px solid var(--surface-default-color);
11 | border-radius: 8px;
12 | box-sizing: border-box;
13 | transition: 200ms all ease-out;
14 | overflow: hidden;
15 | cursor: default;
16 | }
17 |
18 |
19 |
20 | browser-widget-image-article {
21 | display: block;
22 | position: relative;
23 | width: 100%;
24 | height: auto;
25 | text-align: center;
26 | overflow: hidden;
27 | }
28 |
29 | browser-widget-image-article img {
30 | display: block;
31 | width: auto;
32 | max-width: 100%;
33 | height: auto;
34 | max-height: 100%;
35 | margin: 0px auto;
36 | padding: 0px;
37 | }
38 |
39 |
40 |
41 | browser-widget-image-footer {
42 | display: block;
43 | height: 32px;
44 | margin: 16px 0px 0px 0px;
45 | padding: 0px;
46 | text-align: right;
47 | }
48 |
49 | browser-widget-image-footer > button {
50 | margin-left: 16px;
51 | }
52 |
53 | browser-widget-image-footer > button[data-action="download"]:before { content: '\e2c4'; }
54 | browser-widget-image-footer > button[data-action="fullscreen"]:before { content: '\e5d0'; }
55 |
56 |
57 |
58 | @media screen and (max-width: 800px) {
59 |
60 | browser-widget-image {
61 | width: auto;
62 | }
63 |
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/browser/package/archlinux/PKGBUILD:
--------------------------------------------------------------------------------
1 |
2 | pkgname=tholian-browser
3 | pkgver=VERSION
4 | pkgrel=1
5 | pkgdesc="Secure, Peer-to-Peer, Private and Automateable Web Browser/Scraper/Proxy"
6 | arch=(any)
7 | url="https://tholian.network/index.html"
8 | license=("GPL")
9 | depends=("electron")
10 | conflicts=("tholian-stealth")
11 |
12 |
13 |
14 | package() {
15 |
16 | mkdir -p "$pkgdir/usr/bin";
17 | mkdir -p "$pkgdir/usr/lib/tholian/browser";
18 | mkdir -p "$pkgdir/usr/lib/tholian/stealth";
19 | mkdir -p "$pkgdir/usr/lib/tholian/stealth/vendor/cache/headers";
20 | mkdir -p "$pkgdir/usr/lib/tholian/stealth/vendor/cache/payload";
21 | mkdir -p "$pkgdir/usr/share/applications";
22 |
23 | cp -r $srcdir/browser/* "$pkgdir/usr/lib/tholian/browser/";
24 | find "$pkgdir/usr/lib/tholian/browser" -type d -exec chmod 755 {} \;
25 | find "$pkgdir/usr/lib/tholian/browser" -type f -exec chmod 644 {} \;
26 |
27 | cp -r $srcdir/stealth/* "$pkgdir/usr/lib/tholian/stealth/";
28 | find "$pkgdir/usr/lib/tholian/stealth" -type d -exec chmod 755 {} \;
29 | find "$pkgdir/usr/lib/tholian/stealth" -type f -exec chmod 644 {} \;
30 |
31 | install -Dm644 "$srcdir/tholian-browser.desktop" "$pkgdir/usr/share/applications/tholian-browser.desktop";
32 | install -Dm755 "$srcdir/tholian-browser.js" "$pkgdir/usr/bin/tholian-browser";
33 | install -Dm644 "$srcdir/tholian-browser.svg" "$pkgdir/usr/share/icons/tholian-browser.svg";
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/browser/design/sheet/Session.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { console } from '../../extern/base.mjs';
3 | import { Element } from '../Element.mjs';
4 | import { Widget } from '../Widget.mjs';
5 |
6 |
7 |
8 | const Session = function(browser) {
9 |
10 | this.element = new Element('browser-sheet-session', [
11 | '',
12 | '\tSessions ',
13 | ' ',
14 | '',
15 | '\tTabs ',
16 | ' ',
17 | ]);
18 |
19 | this.session = null;
20 | this.sessions = [];
21 |
22 |
23 | this.element.on('contextmenu', (e) => {
24 | e.preventDefault();
25 | e.stopPropagation();
26 | });
27 |
28 | this.element.on('show', () => {
29 |
30 | browser.client.services.session.read({}, (session) => {
31 |
32 | browser.client.services.session.query({
33 | domain: '*'
34 | }, (sessions) => {
35 |
36 | // Make sure reference is to same Array
37 | session = sessions.find((s) => s.domain === session.domain) || null;
38 |
39 | this.session = session.domain;
40 | this.sessions = sessions;
41 |
42 | console.info(session);
43 | console.warn(sessions);
44 |
45 | });
46 |
47 |
48 | });
49 |
50 | this.element.state('active');
51 |
52 | });
53 |
54 | this.element.on('hide', () => {
55 | this.element.state('');
56 | });
57 |
58 |
59 | Widget.call(this);
60 |
61 | };
62 |
63 |
64 | Session.prototype = Object.assign({}, Widget.prototype);
65 |
66 |
67 | export { Session };
68 |
69 |
--------------------------------------------------------------------------------
/stealth/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Stealth Service
3 |
4 | The Stealth Service is a web daemon that allows to load, parse, filter
5 | and render web sites from both a local cache and the interwebz.
6 |
7 | The idea is that the [Browser UI](../browser) is also delivered by
8 | the Stealth Service, so that other platforms (like Android or iOS
9 | tablets and smartphones) can simply access a Stealth instance by
10 | visiting it with any modern Web Browser and to use the Progressive
11 | Web App in fullscreen mode.
12 |
13 |
14 | ## Quickstart
15 |
16 | - Install [node.js](https://nodejs.org/en/download) latest (minimum version `12`).
17 |
18 | ```bash
19 | cd /path/to/stealth;
20 |
21 | # Build
22 | node ./stealth/make.mjs;
23 |
24 | # Show Help
25 | node ./stealth/stealth.mjs;
26 | ```
27 |
28 |
29 | ## Building and Packaging
30 |
31 | ```bash
32 | cd /path/to/stealth;
33 |
34 | # Build
35 | node ./stealth/make.mjs /path/to/sandbox;
36 |
37 | # Package
38 | node ./stealth/make.mjs pack /path/to/sandbox;
39 | ```
40 |
41 |
42 | ## ECMAScript Usage
43 |
44 | The Stealth Library exports a `default` export that contains a namespaced
45 | `Object`. Due to otherwise conflicting names, there are no separately
46 | named exports available.
47 |
48 | ```javascript
49 | // Import Stealth Library
50 | import stealth from './stealth/index.mjs';
51 | ```
52 |
53 |
54 | ## NPM Usage
55 |
56 | ```javascript
57 | // Import Stealth Library
58 | import stealth from 'stealth/stealth';
59 | ```
60 |
61 |
--------------------------------------------------------------------------------
/covert/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": false,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": "eslint:recommended",
8 | "parser": "@babel/eslint-parser",
9 | "parserOptions": {
10 | "ecmaVersion": 2018,
11 | "sourceType": "module",
12 | "requireConfigFile": false
13 | },
14 | "overrides": [{
15 | "files": [ "*.mjs" ],
16 | "rules": {}
17 | }, {
18 | "files": [ "extern/base.mjs" ],
19 | "rules": {
20 | "no-control-regex": "off",
21 | "no-undef": "off"
22 | }
23 | }],
24 | "rules": {
25 | "no-restricted-globals": [
26 | "error",
27 | { "name": "__dirname" },
28 | { "name": "__filename" },
29 | { "name": "Buffer" },
30 | { "name": "console" },
31 | { "name": "exports" },
32 | { "name": "module" },
33 | { "name": "process" },
34 | { "name": "require" },
35 | { "name": "TextDecoder" },
36 | { "name": "TextEncoder" },
37 | { "name": "URL" },
38 | { "name": "URLSearchParams" }
39 | ],
40 | "arrow-parens": [
41 | "error",
42 | "always"
43 | ],
44 | "indent": [
45 | "error",
46 | "tab"
47 | ],
48 | "linebreak-style": [
49 | "error",
50 | "unix"
51 | ],
52 | "no-unused-vars": [
53 | "error", {
54 | "argsIgnorePattern": "^(assert)$"
55 | }
56 | ],
57 | "quotes": [
58 | "error",
59 | "single"
60 | ],
61 | "semi": [
62 | "error",
63 | "always"
64 | ]
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/base/review/Set.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import { Set, isSet } from '../source/Set.mjs';
4 |
5 |
6 |
7 | describe('Set.isSet()', function(assert) {
8 |
9 | let set1 = new Set();
10 | let set2 = new Set([ 1, 3, 3, 7 ]);
11 |
12 | assert(typeof Set.isSet, 'function');
13 |
14 | assert(Set.isSet(set1), true);
15 | assert(Set.isSet(set2), true);
16 |
17 | });
18 |
19 | describe('isSet()', function(assert) {
20 |
21 | let set1 = new Set();
22 | let set2 = new Set([ 1, 3, 3, 7 ]);
23 |
24 | assert(typeof isSet, 'function');
25 |
26 | assert(isSet(set1), true);
27 | assert(isSet(set2), true);
28 |
29 | });
30 |
31 | describe('Set.prototype.toString()', function(assert) {
32 |
33 | let set1 = new Set();
34 | let set2 = new Set([ 1, 3, 3, 7 ]);
35 |
36 | assert(Object.prototype.toString.call(set1), '[object Set]');
37 | assert(Object.prototype.toString.call(set2), '[object Set]');
38 |
39 | assert((set1).toString(), '[object Set]');
40 | assert((set2).toString(), '[object Set]');
41 |
42 | });
43 |
44 | describe('Set.prototype.valueOf()', function(assert) {
45 |
46 | let set1 = new Set();
47 | let set2 = new Set([ 1, 3, 3, 7 ]);
48 |
49 | assert((set1).valueOf(), new Set());
50 | assert((set2).valueOf(), new Set([ 1, 3, 7 ]));
51 |
52 | assert(JSON.stringify(set1), '{}');
53 | assert(JSON.stringify(set2), '{}');
54 |
55 | });
56 |
57 |
58 | export default finish('base/Set', {
59 | internet: false,
60 | network: false
61 | });
62 |
63 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stealth",
3 | "description": "Tholian® Stealth - Secure, Peer-to-Peer, Private and Automatable Web Browser/Scraper/Proxy",
4 | "private": true,
5 | "version": "X0:2021-11-11",
6 | "module": "./stealth/index.mjs",
7 | "bin": {
8 | "browser": "./browser/browser.mjs",
9 | "covert": "./covert/covert.mjs",
10 | "stealth": "./stealth/stealth.mjs"
11 | },
12 | "scripts": {
13 | "lint": "npx eslint ./base ./browser ./covert ./stealth",
14 | "postinstall": "node ./make.mjs build",
15 | "clean": "node ./make.mjs clean",
16 | "clean:base": "node ./base/make.mjs clean",
17 | "clean:browser": "node ./browser/make.mjs clean",
18 | "clean:covert": "node ./covert/make.mjs clean",
19 | "clean:stealth": "node ./stealth/make.mjs clean",
20 | "build": "node ./make.mjs build",
21 | "build:base": "node ./base/make.mjs build",
22 | "build:browser": "node ./browser/make.mjs build",
23 | "build:covert": "node ./covert/make.mjs build",
24 | "build:stealth": "node ./stealth/make.mjs build"
25 | },
26 | "exports": {
27 | "./base": {
28 | "import": "./base/index.mjs",
29 | "browser": "./base/build/browser.mjs",
30 | "node": "./base/build/node.mjs"
31 | },
32 | "./browser": {
33 | "browser": "./browser/index.mjs"
34 | },
35 | "./covert": {
36 | "import": "./covert/index.mjs"
37 | },
38 | "./stealth": {
39 | "import": "./stealth/index.mjs"
40 | }
41 | },
42 | "devDependencies": {
43 | "@babel/eslint-parser": "^7.15.8",
44 | "eslint": "^8.1.0"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/browser/design/common/index.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {
3 | font-family: 'icon';
4 | src: url('icon.woff') format('woff');
5 | font-variant: normal;
6 | font-weight: normal;
7 | font-style: normal;
8 | }
9 |
10 | @font-face {
11 | font-family: 'crystalline';
12 | src: url('crystalline-light.woff') format('woff');
13 | font-variant: normal;
14 | font-weight: 250;
15 | font-style: normal;
16 | }
17 |
18 | @font-face {
19 | font-family: 'crystalline';
20 | src: url('crystalline-regular.woff') format('woff');
21 | font-variant: normal;
22 | font-weight: normal;
23 | font-style: normal;
24 | }
25 |
26 | @font-face {
27 | font-family: 'crystalline';
28 | src: url('crystalline-medium.woff') format('woff');
29 | font-variant: normal;
30 | font-weight: 500;
31 | font-style: normal;
32 | }
33 |
34 | @font-face {
35 | font-family: 'crystalline';
36 | src: url('crystalline-bold.woff') format('woff');
37 | font-variant: normal;
38 | font-weight: 700;
39 | font-style: normal;
40 | }
41 |
42 | @font-face {
43 | font-family: 'museo';
44 | src: url('museo-regular.woff') format('woff');
45 | font-variant: normal;
46 | font-weight: normal;
47 | font-style: normal;
48 | }
49 |
50 | @font-face {
51 | font-family: 'museo';
52 | src: url('museo-medium.woff') format('woff');
53 | font-variant: normal;
54 | font-weight: 500;
55 | font-style: normal;
56 | }
57 |
58 | @font-face {
59 | font-family: 'vera-mono';
60 | src: url('vera-mono.woff') format('woff');
61 | font-variant: normal;
62 | font-weight: normal;
63 | font-style: normal;
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/base/review/Number.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import { Number, isNumber } from '../source/Number.mjs';
4 |
5 |
6 |
7 | describe('Number.isNumber()', function(assert) {
8 |
9 | let number1 = 13.37;
10 | let number2 = new Number(13.37);
11 |
12 | assert(typeof Number.isNumber, 'function');
13 |
14 | assert(Number.isNumber(number1), true);
15 | assert(Number.isNumber(number2), true);
16 |
17 | });
18 |
19 | describe('isNumber()', function(assert) {
20 |
21 | let number1 = 13.37;
22 | let number2 = new Number(13.37);
23 |
24 | assert(typeof isNumber, 'function');
25 |
26 | assert(isNumber(number1), true);
27 | assert(isNumber(number2), true);
28 |
29 | });
30 |
31 | describe('Number.prototype.toString()', function(assert) {
32 |
33 | let number1 = 13.37;
34 | let number2 = new Number(13.37);
35 |
36 | assert(Object.prototype.toString.call(number1), '[object Number]');
37 | assert(Object.prototype.toString.call(number2), '[object Number]');
38 |
39 | assert((number1).toString(), '13.37');
40 | assert((number2).toString(), '13.37');
41 |
42 | });
43 |
44 | describe('Number.prototype.valueOf()', function(assert) {
45 |
46 | let number1 = 13.37;
47 | let number2 = new Number(13.37);
48 |
49 | assert((number1).valueOf(), 13.37);
50 | assert((number2).valueOf(), 13.37);
51 |
52 | assert(JSON.stringify(number1), '13.37');
53 | assert(JSON.stringify(number2), '13.37');
54 |
55 | });
56 |
57 |
58 | export default finish('base/Number', {
59 | internet: false,
60 | network: false
61 | });
62 |
63 |
--------------------------------------------------------------------------------
/covert/index.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { after, before, describe, finish } from './source/Review.mjs';
3 | import { Covert, isCovert } from './source/Covert.mjs';
4 | import { ENVIRONMENT } from './source/ENVIRONMENT.mjs';
5 | import { Filesystem, isFilesystem } from './source/Filesystem.mjs';
6 | import { Linter, isLinter } from './source/Linter.mjs';
7 | import { Network, isNetwork } from './source/Network.mjs';
8 | import { Renderer, isRenderer } from './source/Renderer.mjs';
9 | import { Results, isResults } from './source/Results.mjs';
10 | import { Review, isReview } from './source/Review.mjs';
11 | import { Timeline, isTimeline } from './source/Timeline.mjs';
12 |
13 | export { after, before, describe, finish } from './source/Review.mjs';
14 | export { ENVIRONMENT } from './source/ENVIRONMENT.mjs';
15 |
16 |
17 |
18 | export default {
19 |
20 | after,
21 | before,
22 | describe,
23 | finish,
24 |
25 | isCovert: isCovert,
26 | isFilesystem: isFilesystem,
27 | isLinter: isLinter,
28 | isNetwork: isNetwork,
29 | isRenderer: isRenderer,
30 | isResults: isResults,
31 | isReview: isReview,
32 | isTimeline: isTimeline,
33 |
34 | Covert: Covert,
35 | ENVIRONMENT: ENVIRONMENT,
36 | Filesystem: Filesystem,
37 | Linter: Linter,
38 | Network: Network,
39 | Renderer: Renderer,
40 | Results: Results,
41 | Review: Review,
42 | Timeline: Timeline
43 |
44 | };
45 |
46 |
--------------------------------------------------------------------------------
/guide/security/Network.md:
--------------------------------------------------------------------------------
1 |
2 | # Network Security Guide
3 |
4 | (This Document is Work in Progress)
5 |
6 | The Stealth Service allows to relay traffic with multiple mechanisms,
7 | and allows to share network connections with its Peers in a local
8 | manner and/or global manner, so it's hard to get a hold of it in terms
9 | of networked state complexity or whether or not it can be abused to
10 | identify an end-user uniquely.
11 |
12 | Every Stealth Service also contains:
13 |
14 | - An HTTP/S Proxy (that supports `CONNECT` and `GET`) on port `65432`.
15 | - A Webserver that serves the Browser UI at port `65432` and path `/browser/*`.
16 | - A Websocket Services that serves the Peer-to-Peer API on port `65432`.
17 | - A Multicast DNS-SD Service that interacts with other local Stealth Peers on port `5353`.
18 | - A DNS Router that can resolve DNS Requests for other Peers on port `65432`.
19 | - A SOCKS Proxy running on port `65432`.
20 |
21 |
22 | ## Attack Vector: TCP/UDP Manipulation
23 |
24 | ## Attack Vector: TCP/UDP Fingerprinting
25 |
26 | ## Attack Vector: NAT Blocking
27 |
28 | ## Attack Vector: DHT / Radar Access Blocking
29 |
30 | ## Attack Vector: DNS Manipulation
31 |
32 | ## Attack Vector: DNS Tracking
33 |
34 | ## Attack Vector: HTTP Downgrade Attack
35 |
36 | ## Attack Vector: TLS Downgrade Attack(s)
37 |
38 | ## Attack Vector: TLS Timing/Side-Channel Attack(s)
39 |
40 | ## Attack Vector: HTTP/S Traffic Correlation Tracking
41 |
42 | ## Attack Vector: Multicast DNS-SD Manipulation
43 |
44 | ## Attack Vector: Multicast DNS-SD Tracking
45 |
46 |
--------------------------------------------------------------------------------
/browser/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": false
6 | },
7 | "extends": "eslint:recommended",
8 | "parser": "@babel/eslint-parser",
9 | "parserOptions": {
10 | "ecmaVersion": 2018,
11 | "sourceType": "module",
12 | "requireConfigFile": false
13 | },
14 | "globals": {
15 | "Buffer": true
16 | },
17 | "overrides": [{
18 | "files": [ "*.mjs" ],
19 | "rules": {}
20 | }, {
21 | "files": [ "app/index.js" ],
22 | "env": {
23 | "node": true
24 | },
25 | "globals": {
26 | "require": true
27 | }
28 | }, {
29 | "files": [ "app/console.js" ],
30 | "env": {
31 | "node": true
32 | }
33 | }, {
34 | "files": [ "service.js" ],
35 | "rules": {
36 | "no-control-regex": "off",
37 | "no-console": "off",
38 | "no-restricted-globals": "off"
39 | }
40 | }, {
41 | "files": [ "extern/base.mjs" ],
42 | "rules": {
43 | "no-undef": "off"
44 | }
45 | }],
46 | "rules": {
47 | "no-restricted-globals": [
48 | "error",
49 | { "name": "console" },
50 | { "name": "location" },
51 | { "name": "URL" },
52 | { "name": "URLSearchParams" }
53 | ],
54 | "arrow-parens": [
55 | "error",
56 | "always"
57 | ],
58 | "indent": [
59 | "error",
60 | "tab"
61 | ],
62 | "linebreak-style": [
63 | "error",
64 | "unix"
65 | ],
66 | "no-unused-vars": [
67 | "error", {
68 | "varsIgnorePattern": "^(BROWSER|ELEMENTS|WIDGETS)$"
69 | }
70 | ],
71 | "quotes": [
72 | "error",
73 | "single"
74 | ],
75 | "semi": [
76 | "error",
77 | "always"
78 | ]
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/base/review/RegExp.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import { RegExp, isRegExp } from '../source/RegExp.mjs';
4 |
5 |
6 |
7 | describe('RegExp.isRegExp()', function(assert) {
8 |
9 | let regexp1 = /foo\/bar/g;
10 | let regexp2 = new RegExp('foo/bar', 'g');
11 |
12 | assert(typeof RegExp.isRegExp, 'function');
13 |
14 | assert(RegExp.isRegExp(regexp1), true);
15 | assert(RegExp.isRegExp(regexp2), true);
16 |
17 | });
18 |
19 | describe('isRegExp()', function(assert) {
20 |
21 | let regexp1 = /foo\/bar/g;
22 | let regexp2 = new RegExp('foo/bar', 'g');
23 |
24 | assert(typeof isRegExp, 'function');
25 |
26 | assert(isRegExp(regexp1), true);
27 | assert(isRegExp(regexp2), true);
28 |
29 | });
30 |
31 | describe('RegExp.prototype.toString()', function(assert) {
32 |
33 | let regexp1 = /foo\/bar/g;
34 | let regexp2 = new RegExp('foo/bar', 'g');
35 |
36 | assert(Object.prototype.toString.call(regexp1), '[object RegExp]');
37 | assert(Object.prototype.toString.call(regexp2), '[object RegExp]');
38 |
39 | assert((regexp1).toString(), '/foo\\/bar/g');
40 | assert((regexp2).toString(), '/foo\\/bar/g');
41 |
42 | });
43 |
44 | describe('RegExp.prototype.valueOf()', function(assert) {
45 |
46 | let regexp1 = /foo\/bar/g;
47 | let regexp2 = new RegExp('foo/bar', 'g');
48 |
49 | assert((regexp1).valueOf(), regexp1);
50 | assert((regexp2).valueOf(), regexp2);
51 |
52 | assert(JSON.stringify(regexp1), '{}');
53 | assert(JSON.stringify(regexp2), '{}');
54 |
55 | });
56 |
57 |
58 | export default finish('base/RegExp', {
59 | internet: false,
60 | network: false
61 | });
62 |
63 |
--------------------------------------------------------------------------------
/base/source/Array.mjs:
--------------------------------------------------------------------------------
1 |
2 | export const Array = (typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : this)).Array;
3 |
4 | if (typeof Array.isArray !== 'function') {
5 |
6 | Array.isArray = function(arr) {
7 | return Object.prototype.toString.call(arr) === '[object Array]';
8 | };
9 |
10 | }
11 |
12 | if (typeof Array.prototype.remove !== 'function') {
13 |
14 | Array.prototype.remove = function(value) {
15 |
16 | let index = this.indexOf(value);
17 |
18 | while (index !== -1) {
19 |
20 | this.splice(index, 1);
21 |
22 | index = this.indexOf(value);
23 |
24 | }
25 |
26 | return this;
27 |
28 | };
29 |
30 | }
31 |
32 | if (typeof Array.prototype.removeEvery !== 'function') {
33 |
34 | Array.prototype.removeEvery = function(predicate/*, thisArg */) {
35 |
36 | if (this === null || this === undefined) {
37 | throw new TypeError('Array.prototype.removeEvery called on null or undefined');
38 | }
39 |
40 | if (typeof predicate !== 'function') {
41 | throw new TypeError('predicate must be a function');
42 | }
43 |
44 |
45 | let list = Object(this);
46 | let length = list.length >>> 0;
47 | let thisArg = arguments.length >= 2 ? arguments[1] : void 0;
48 | let value;
49 |
50 | for (let i = 0; i < length; i++) {
51 |
52 | if (i in list) {
53 |
54 | value = list[i];
55 |
56 | if (!!predicate.call(thisArg, value, i, list) === true) {
57 | this.splice(i, 1);
58 | length--;
59 | i--;
60 | }
61 |
62 | }
63 |
64 | }
65 |
66 | return this;
67 |
68 | };
69 |
70 | }
71 |
72 | export const isArray = Array.isArray;
73 |
74 |
--------------------------------------------------------------------------------
/browser/internal/debug.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { ENVIRONMENT } from '../source/ENVIRONMENT.mjs';
3 | import { Element } from '../design/Element.mjs';
4 | import { Settings } from '../design/card/Settings.mjs';
5 | import { Browser } from '../internal/debug/Browser.mjs';
6 | import { URL } from '../source/parser/URL.mjs';
7 |
8 |
9 |
10 | let browser = window.parent.BROWSER || null;
11 | if (browser !== null) {
12 |
13 | let browser_settings = Browser.from(browser.settings, [ 'tabs', 'sessions' ]);
14 | let site_settings = Settings.from({ domain: '' }, [ 'beacons', 'blockers', 'echos', 'hosts', 'modes', 'peers', 'policies', 'redirects', 'tasks' ]);
15 |
16 | if (URL.isURL(ENVIRONMENT.flags.url) === true) {
17 |
18 | let domain = URL.toDomain(ENVIRONMENT.flags.url);
19 | if (domain !== null) {
20 | site_settings.value({ domain: domain });
21 | }
22 |
23 | let footer = site_settings.query('browser-card-settings-footer');
24 | if (footer !== null) {
25 |
26 | let button = new Element('button');
27 |
28 | button.attr('title', 'Open URL in new Tab');
29 | button.attr('data-action', 'open');
30 |
31 | button.on('click', () => {
32 |
33 | let tab = browser.open(ENVIRONMENT.flags.url.link);
34 | if (tab !== null) {
35 | browser.show(tab);
36 | }
37 |
38 | });
39 |
40 | button.render(footer);
41 |
42 | }
43 |
44 | }
45 |
46 | let body = Element.query('body');
47 | if (body !== null) {
48 |
49 | site_settings.render(body);
50 | site_settings.emit('show');
51 |
52 | browser_settings.render(body);
53 | browser_settings.emit('show');
54 |
55 | }
56 |
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/stealth/source/optimizer/CSS.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { isFunction, isObject } from '../../extern/base.mjs';
3 |
4 |
5 |
6 | const Optimizer = {
7 |
8 | check: function(ref, config, callback) {
9 |
10 | ref = isObject(ref) ? ref : null;
11 | config = isObject(config) ? config : null;
12 | callback = isFunction(callback) ? callback : null;
13 |
14 |
15 | if (ref !== null && config !== null && callback !== null) {
16 |
17 | let path = ref.path || null;
18 | if (path !== null && path.endsWith('.css')) {
19 | callback(true);
20 | } else {
21 | callback(false);
22 | }
23 |
24 | } else if (ref !== null && config !== null) {
25 |
26 | let path = ref.path || null;
27 | if (path !== null && path.endsWith('.css')) {
28 | return true;
29 | } else {
30 | return false;
31 | }
32 |
33 | } else if (callback !== null) {
34 | callback(false);
35 | } else {
36 | return false;
37 | }
38 |
39 | },
40 |
41 | optimize: function(ref, config, callback) {
42 |
43 | ref = isObject(ref) ? ref : null;
44 | config = isObject(config) ? config : null;
45 | callback = isFunction(callback) ? callback : null;
46 |
47 |
48 | if (ref !== null && config !== null && callback !== null) {
49 |
50 | // TODO: Implement me
51 |
52 | } else if (ref !== null && config !== null) {
53 |
54 | // TODO: Implement me
55 |
56 | } else if (callback !== null) {
57 | callback(null);
58 | } else {
59 | return null;
60 | }
61 |
62 | }
63 |
64 | };
65 |
66 |
67 | export const check = Optimizer.check;
68 | export const optimize = Optimizer.optimize;
69 |
70 | export { Optimizer };
71 |
72 |
--------------------------------------------------------------------------------
/base/review/String.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import { String, isString } from '../source/String.mjs';
4 |
5 |
6 |
7 | describe('String.isString()', function(assert) {
8 |
9 | let string1 = 'example.com';
10 | let string2 = new String('example.com');
11 |
12 | assert(typeof String.isString, 'function');
13 | assert(String.isString, isString);
14 |
15 | assert(String.isString(string1), true);
16 | assert(String.isString(string2), true);
17 |
18 | });
19 |
20 | describe('isString()', function(assert) {
21 |
22 | let string1 = 'example.com';
23 | let string2 = new String('example.com');
24 |
25 | assert(typeof isString, 'function');
26 |
27 | assert(isString(string1), true);
28 | assert(isString(string2), true);
29 |
30 | });
31 |
32 | describe('String.prototype.toString()', function(assert) {
33 |
34 | let string1 = 'example.com';
35 | let string2 = new String('example.com');
36 |
37 | assert(Object.prototype.toString.call(string1), '[object String]');
38 | assert(Object.prototype.toString.call(string2), '[object String]');
39 |
40 | assert((string1).toString(), 'example.com');
41 | assert((string2).toString(), 'example.com');
42 |
43 | });
44 |
45 | describe('String.prototype.valueOf()', function(assert) {
46 |
47 | let string1 = 'example.com';
48 | let string2 = new String('example.com');
49 |
50 | assert((string1).valueOf(), 'example.com');
51 | assert((string2).valueOf(), 'example.com');
52 |
53 | assert(JSON.stringify(string1), '"example.com"');
54 | assert(JSON.stringify(string2), '"example.com"');
55 |
56 | });
57 |
58 |
59 | export default finish('base/String', {
60 | internet: false,
61 | network: false
62 | });
63 |
64 |
--------------------------------------------------------------------------------
/base/review/Date.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import { Date, isDate } from '../source/Date.mjs';
4 |
5 |
6 |
7 | describe('Date.isDate()', function(assert) {
8 |
9 | let date1 = new Date('01.02.20 13:37Z');
10 | let date2 = new Date('2020-02-01T13:37:00Z');
11 |
12 | assert(typeof Date.isDate, 'function');
13 |
14 | assert(Date.isDate(date1), true);
15 | assert(Date.isDate(date2), true);
16 |
17 | });
18 |
19 |
20 | describe('isDate()', function(assert) {
21 |
22 | let date1 = new Date('01.02.20 13:37Z');
23 | let date2 = new Date('2020-02-01T13:37:00Z');
24 |
25 | assert(typeof isDate, 'function');
26 |
27 | assert(isDate(date1), true);
28 | assert(isDate(date2), true);
29 |
30 | });
31 |
32 | describe('Date.prototype.toISOString()', function(assert) {
33 |
34 | let date1 = new Date('02.01.2020 13:37Z');
35 | let date2 = new Date('2020-02-01T13:37:00Z');
36 |
37 | assert(Object.prototype.toString.call(date1), '[object Date]');
38 | assert(Object.prototype.toString.call(date2), '[object Date]');
39 |
40 | assert((date1).toISOString(), '2020-02-01T13:37:00.000Z');
41 | assert((date2).toISOString(), '2020-02-01T13:37:00.000Z');
42 |
43 | });
44 |
45 | describe('Date.prototype.valueOf()', function(assert) {
46 |
47 | let date1 = new Date('03.04.2020 13:37Z');
48 | let date2 = new Date('2020-03-04T13:37:00Z');
49 |
50 | assert((date1).valueOf(), 1583329020000);
51 | assert((date2).valueOf(), 1583329020000);
52 |
53 | assert(JSON.stringify(date1), '"2020-03-04T13:37:00.000Z"');
54 | assert(JSON.stringify(date2), '"2020-03-04T13:37:00.000Z"');
55 |
56 | });
57 |
58 |
59 | export default finish('base/Date', {
60 | internet: false,
61 | network: false
62 | });
63 |
64 |
--------------------------------------------------------------------------------
/browser/internal/settings.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { ENVIRONMENT } from '../source/ENVIRONMENT.mjs';
3 | import { Element } from '../design/Element.mjs';
4 | import { Widget } from '../design/Widget.mjs';
5 | import { Interface } from '../design/card/Interface.mjs';
6 | import { Internet } from '../design/card/Internet.mjs';
7 | import { Settings } from '../design/card/Settings.mjs';
8 |
9 |
10 |
11 | const render_widget = (widget) => {
12 |
13 | let body = Element.query('body');
14 | if (body !== null && widget !== null) {
15 |
16 | widget.render(body);
17 | widget.emit('show');
18 |
19 | }
20 |
21 | };
22 |
23 |
24 |
25 | let browser = window.parent.BROWSER || null;
26 | if (browser !== null) {
27 |
28 | let elements = {};
29 | let widgets = {};
30 |
31 |
32 | elements['profile'] = Element.query('[data-key="profile"]');
33 |
34 | if (elements['profile'] !== null) {
35 |
36 | browser.client.services['settings'].info(null, (response) => {
37 |
38 | if (response.profile !== null) {
39 | elements['profile'].value(response.profile);
40 | }
41 |
42 | });
43 |
44 | }
45 |
46 |
47 | widgets['interface'] = Interface.from(browser.settings['interface']);
48 | widgets['internet'] = Internet.from(browser.settings['internet']);
49 | widgets['settings'] = Settings.from({ domain: '' }, [ 'beacons', 'echos', 'hosts', 'modes', 'policies', 'redirects', 'tasks' ]);
50 |
51 | render_widget(widgets['interface']);
52 | render_widget(widgets['internet']);
53 | render_widget(widgets['settings']);
54 |
55 |
56 | if (ENVIRONMENT.flags.debug === true) {
57 |
58 | window.ELEMENTS = elements;
59 | window.WIDGETS = widgets;
60 |
61 | window.Element = Element;
62 | window.Widget = Widget;
63 |
64 | }
65 |
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/stealth/source/parser/CSS.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { isArray, isBuffer, isObject, isString } from '../../extern/base.mjs';
3 | import { Parser } from '../../source/language/Parser.mjs';
4 | import { GRAMMAR } from '../../source/language/CSS/GRAMMAR.mjs';
5 | import { SYNTAX } from '../../source/language/CSS/SYNTAX.mjs';
6 |
7 |
8 |
9 | const CSS = {
10 |
11 | compare: function(a, b) {
12 |
13 | // TODO: Can CSS trees be compared to each other?
14 |
15 | },
16 |
17 | isCSS: function(payload) {
18 |
19 | payload = isObject(payload) ? payload : null;
20 |
21 |
22 | if (payload !== null) {
23 |
24 | // TODO: Validate Payload (tree)
25 |
26 | }
27 |
28 | return false;
29 |
30 | },
31 |
32 | parse: function(buf_or_str) {
33 |
34 | let raw = null;
35 |
36 | if (isBuffer(buf_or_str) === true) {
37 | raw = buf_or_str.toString('utf8');
38 | } else if (isString(buf_or_str) === true) {
39 | raw = buf_or_str;
40 | }
41 |
42 |
43 | let parser = new Parser({ grammar: GRAMMAR, syntax: SYNTAX });
44 | let tree = { type: 'root', rules: [] };
45 |
46 | if (raw !== null) {
47 |
48 | tree = parser.parse(raw);
49 |
50 | }
51 |
52 | return tree;
53 |
54 | },
55 |
56 | render: function(tree) {
57 |
58 | // TODO: Render CSS tree into Buffer
59 |
60 | },
61 |
62 | sort: function(array) {
63 |
64 | array = isArray(array) ? array : null;
65 |
66 |
67 | if (array !== null) {
68 |
69 | return array.filter((tree) => {
70 | return CSS.isCSS(tree) === true;
71 | }).sort((a, b) => {
72 | return CSS.compare(a, b);
73 | });
74 |
75 | }
76 |
77 |
78 | return [];
79 |
80 | }
81 |
82 | };
83 |
84 |
85 | export { CSS };
86 |
87 |
--------------------------------------------------------------------------------
/stealth/vendor/policies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "domain": "amazon.com",
4 | "policies": [
5 | {
6 | "path": "/s/*",
7 | "query": "k"
8 | }
9 | ]
10 | },
11 | {
12 | "domain": "bing.com",
13 | "policies": [
14 | {
15 | "path": "/search",
16 | "query": "q&search"
17 | }
18 | ]
19 | },
20 | {
21 | "domain": "clickserve.dartsearch.net",
22 | "policies": [
23 | {
24 | "path": "/link/click",
25 | "query": "ds_dest_url"
26 | }
27 | ]
28 | },
29 | {
30 | "domain": "ebay.com",
31 | "policies": [
32 | {
33 | "path": "/sch/i.html",
34 | "query": "_nkw&_sacat&LH_*"
35 | }
36 | ]
37 | },
38 | {
39 | "domain": "google.com",
40 | "policies": [
41 | {
42 | "path": "/search",
43 | "query": "q&tbm"
44 | },
45 | {
46 | "path": "/aclk",
47 | "query": "ai&sa"
48 | }
49 | ]
50 | },
51 | {
52 | "domain": "imdb.com",
53 | "policies": [
54 | {
55 | "path": "/find",
56 | "query": "q&s&ttype"
57 | }
58 | ]
59 | },
60 | {
61 | "domain": "reddit.com",
62 | "policies": [
63 | {
64 | "path": "/r/*/controversial/",
65 | "query": "sort&t"
66 | },
67 | {
68 | "path": "/r/*/top/",
69 | "query": "t"
70 | }
71 | ]
72 | },
73 | {
74 | "domain": "shutterstock.com",
75 | "policies": [
76 | {
77 | "path": "/search/",
78 | "query": "age&artists&color&editorialðnicity&exclude_keywords&gender&image_type&measurement&min_height&min_width&mreleased&number_of_people&orientation&safe&sort"
79 | }
80 | ]
81 | },
82 | {
83 | "domain": "tholian.network",
84 | "policies": []
85 | },
86 | {
87 | "domain": "youtube.com",
88 | "policies": [
89 | {
90 | "path": "/watch",
91 | "query": "index&list&v"
92 | }
93 | ]
94 | }
95 | ]
96 |
--------------------------------------------------------------------------------
/base/review/Map.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import { Map, isMap } from '../source/Map.mjs';
4 |
5 |
6 |
7 | describe('Map.isMap()', function(assert) {
8 |
9 | let map1 = new Map();
10 | let map2 = new Map([
11 | [ 1, 'l' ],
12 | [ 3, 'e' ],
13 | [ 3, 'e' ],
14 | [ 7, 't' ]
15 | ]);
16 |
17 | assert(typeof Map.isMap, 'function');
18 |
19 | assert(Map.isMap(map1), true);
20 | assert(Map.isMap(map2), true);
21 |
22 | });
23 |
24 | describe('isMap()', function(assert) {
25 |
26 | let map1 = new Map();
27 | let map2 = new Map([
28 | [ 1, 'l' ],
29 | [ 3, 'e' ],
30 | [ 3, 'e' ],
31 | [ 7, 't' ]
32 | ]);
33 |
34 | assert(typeof isMap, 'function');
35 |
36 | assert(isMap(map1), true);
37 | assert(isMap(map2), true);
38 |
39 | });
40 |
41 | describe('Map.prototype.toString()', function(assert) {
42 |
43 | let map1 = new Map();
44 | let map2 = new Map([
45 | [ 1, 'l' ],
46 | [ 3, 'e' ],
47 | [ 3, 'e' ],
48 | [ 7, 't' ]
49 | ]);
50 |
51 | assert(Object.prototype.toString.call(map1), '[object Map]');
52 | assert(Object.prototype.toString.call(map2), '[object Map]');
53 |
54 | assert((map1).toString(), '[object Map]');
55 | assert((map2).toString(), '[object Map]');
56 |
57 | });
58 |
59 | describe('Map.prototype.valueOf()', function(assert) {
60 |
61 | let map1 = new Map();
62 | let map2 = new Map([
63 | [ 1, 'l' ],
64 | [ 3, 'e' ],
65 | [ 3, 'e' ],
66 | [ 7, 't' ]
67 | ]);
68 |
69 | assert((map1).valueOf(), new Map());
70 | assert((map2).valueOf(), new Map([ [1,'l'], [3,'e'], [7,'t'] ]));
71 |
72 | assert(JSON.stringify(map1), '{}');
73 | assert(JSON.stringify(map2), '{}');
74 |
75 | });
76 |
77 |
78 | export default finish('base/Map', {
79 | internet: false,
80 | network: false
81 | });
82 |
83 |
--------------------------------------------------------------------------------
/browser/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Stealth Browser
3 |
4 | The Stealth Browser is a Browser UI which is implemented using
5 | Web Technologies, so that it is possible to change the Browser
6 | Engine or Web View easily.
7 |
8 |
9 | ## URL-Parameter Flags
10 |
11 | - `/browser/index.html?debug=true` is a parameter to toggle
12 | loading/debugging of the UI by disabling the Service Worker.
13 | The default value is `false`.
14 |
15 |
16 | ## Quickstart
17 |
18 | - Install [node.js](https://nodejs.org/en/download) latest (minimum version `12`).
19 | - Install [Electron](https://www.electronjs.org/releases/stable) latest (minimum version `12`).
20 | - (Alternatively) Install [Ungoogled Chromium](https://github.com/Eloston/ungoogled-chromium/releases) latest (minimum version `70`).
21 |
22 | ```bash
23 | cd /path/to/stealth;
24 |
25 | # Build
26 | node ./stealth/make.mjs;
27 |
28 | # Start Stealth Service
29 | node ./stealth/stealth.mjs serve;
30 |
31 | # Build
32 | node ./browser/make.mjs;
33 |
34 | # Open Progressive Web App
35 | node ./browser/browser.mjs;
36 | ```
37 |
38 |
39 | ## Building and Packaging
40 |
41 | ```bash
42 | cd /path/to/stealth;
43 |
44 | # Build
45 | node ./browser/make.mjs /path/to/sandbox;
46 |
47 | # Package
48 | node ./browser/make.mjs pack /path/to/sandbox;
49 | ```
50 |
51 |
52 | ## ECMAScript Usage
53 |
54 | The Browser Library exports a `default` export that contains a namespaced
55 | `Object`. Due to otherwise conflicting names, there are no separately
56 | named exports available.
57 |
58 | As the [extern/base.mjs](./extern/base.mjs) file uses the HTML5 Polyfills
59 | variant of the [Base Library](../base), the Browser Library is also only
60 | compatible with HTML5 Environments.
61 |
62 | ```html
63 |
67 | ```
68 |
69 |
--------------------------------------------------------------------------------
/base/review/Function.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import { Function, isFunction } from '../source/Function.mjs';
4 |
5 |
6 |
7 | describe('Function.isFunction()', function(assert) {
8 |
9 | let function1 = function() {console.log('test');};
10 | let function2 = new Function('console.log(\'test\');');
11 |
12 | assert(typeof Function.isFunction, 'function');
13 |
14 | assert(Function.isFunction(function1), true);
15 | assert(Function.isFunction(function2), true);
16 |
17 | });
18 |
19 | describe('isFunction()', function(assert) {
20 |
21 | let function1 = function() {console.log('test');};
22 | let function2 = new Function('console.log(\'test\');');
23 |
24 | assert(typeof isFunction, 'function');
25 |
26 | assert(isFunction(function1), true);
27 | assert(isFunction(function2), true);
28 |
29 | });
30 |
31 | describe('Function.prototype.toString()', function(assert) {
32 |
33 | let function1 = function() {console.log('test');};
34 | let function2 = new Function('console.log(\'test\');');
35 |
36 | assert(Object.prototype.toString.call(function1), '[object Function]');
37 | assert(Object.prototype.toString.call(function2), '[object Function]');
38 |
39 | assert((function1).toString(), 'function() {console.log(\'test\');}');
40 | assert((function2).toString(), 'function anonymous(\n) {\nconsole.log(\'test\');\n}');
41 |
42 | });
43 |
44 | describe('Function.prototype.valueOf()', function(assert) {
45 |
46 | let function1 = function() {console.log('test');};
47 | let function2 = new Function('console.log(\'test\');');
48 |
49 | assert((function1).valueOf(), function1);
50 | assert((function2).valueOf(), function2);
51 |
52 | assert(JSON.stringify(function1) === undefined);
53 | assert(JSON.stringify(function2) === undefined);
54 |
55 | });
56 |
57 |
58 | export default finish('base/Function', {
59 | internet: false,
60 | network: false
61 | });
62 |
63 |
--------------------------------------------------------------------------------
/covert/review/MODULE.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import COVERT from '../../covert/index.mjs';
4 |
5 |
6 |
7 | describe('MODULE', function(assert) {
8 |
9 | assert(typeof COVERT['Covert'], 'function');
10 | assert(typeof COVERT['ENVIRONMENT'], 'object');
11 | assert(typeof COVERT['Filesystem'], 'function');
12 | assert(typeof COVERT['Linter'], 'function');
13 | assert(typeof COVERT['Network'], 'function');
14 | assert(typeof COVERT['Renderer'], 'function');
15 | assert(typeof COVERT['Results'], 'function');
16 | assert(typeof COVERT['Review'], 'function');
17 | assert(typeof COVERT['Timeline'], 'function');
18 |
19 | assert(typeof COVERT['after'], 'function');
20 | assert(typeof COVERT['before'], 'function');
21 | assert(typeof COVERT['describe'], 'function');
22 | assert(typeof COVERT['finish'], 'function');
23 |
24 | assert(typeof COVERT['isCovert'], 'function');
25 | assert(typeof COVERT['isFilesystem'], 'function');
26 | assert(typeof COVERT['isLinter'], 'function');
27 | assert(typeof COVERT['isNetwork'], 'function');
28 | assert(typeof COVERT['isRenderer'], 'function');
29 | assert(typeof COVERT['isResults'], 'function');
30 | assert(typeof COVERT['isReview'], 'function');
31 | assert(typeof COVERT['isTimeline'], 'function');
32 |
33 | assert(Object.keys(COVERT), [
34 |
35 | 'after',
36 | 'before',
37 | 'describe',
38 | 'finish',
39 |
40 | 'isCovert',
41 | 'isFilesystem',
42 | 'isLinter',
43 | 'isNetwork',
44 | 'isRenderer',
45 | 'isResults',
46 | 'isReview',
47 | 'isTimeline',
48 |
49 | 'Covert',
50 | 'ENVIRONMENT',
51 | 'Filesystem',
52 | 'Linter',
53 | 'Network',
54 | 'Renderer',
55 | 'Results',
56 | 'Review',
57 | 'Timeline'
58 |
59 | ]);
60 |
61 | });
62 |
63 |
64 | export default finish('covert/MODULE', {
65 | internet: false,
66 | network: false
67 | });
68 |
69 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 |
2 | # TODO
3 |
4 | - `progress` event's payload is `null` probably due to packet parser.
5 | - `response` event has wrong `@transfer` headers, these need to be fixed.
6 | - Webproxy Review needs more tests for webview requests.
7 |
8 |
9 | # Cache Service
10 |
11 | - Cache needs meta information that contains a map of datetime, content-length,
12 | last-modified, etag for each URL.
13 |
14 | - Cache is compressed or filtered on disk, which means that original file
15 | sizes have to be persisted to be compareable later; so the cache structure
16 | needs an additional `size` (amount of bytes) header.
17 |
18 |
19 | # Session Service
20 |
21 | - Tab open event
22 | - Tab close event
23 | - Tab navigate event
24 | - Tab refresh event (creates a task with `type=refresh` event for current URL)
25 |
26 |
27 | # `stealth:radar` Page
28 |
29 | - Popular Sites and URLs
30 | - Shareable Topics and Bookmarks/Subscribed Content
31 |
32 | # `stealth:beacons` Page
33 |
34 | - Adapters to extract content, available for `echoes` and its flow graph.
35 | - Export and Synchronize assets per URL pattern (e.g. texts, audios, images and videos).
36 |
37 | # `stealth:echoes` Page
38 |
39 | - User Action Recording Wizard, which records `keyboard`, `mouse`, `click`,
40 | and `touch` events for automation.
41 |
42 | # `stealth:tasks` Page
43 |
44 | - Implementation of a task scheduling wizard that allows to schedule `beacon`
45 | and `echo` actions at specific repeating times and/or a single datetime.
46 |
47 | - Tasks with type `request` and `scrape` should contain incremental checks to
48 | prevent unnecessary downloads. This means check of datetime, content-length, etag
49 | headers etc.
50 |
51 | - Quality of proposal for shared tasks, the broader the options, the more likely
52 | to succeed. Higher quality of proposals lead to more peers adopting the tasks.
53 |
54 | - Debate phase where different tasks and objectives are evaluated.
55 | - Consensus: Same goal, different choices.
56 | - Vote: Different goals, one choice each, usually mutually exclusive.
57 |
58 |
--------------------------------------------------------------------------------
/browser/design/appbar/History.css:
--------------------------------------------------------------------------------
1 |
2 | browser-appbar-history {
3 | display: inline-block;
4 | position: relative;
5 | width: auto;
6 | height: 32px;
7 | margin: 0px;
8 | padding: 0px;
9 | line-height: 32px;
10 | text-align: center;
11 | vertical-align: top;
12 | white-space: nowrap;
13 | user-select: none;
14 | -webkit-user-select: none;
15 | }
16 |
17 | browser-appbar-history button {
18 | display: inline-block;
19 | min-width: 32px;
20 | margin: 0px;
21 | padding: 0px;
22 | line-height: 32px;
23 | height: 32px;
24 | color: var(--element-default-color);
25 | font-size: 24px;
26 | text-align: center;
27 | background: var(--element-default-background);
28 | border: 1px solid transparent;
29 | border-radius: 4px;
30 | box-sizing: border-box;
31 | transition: 200ms all ease-out;
32 | appearance: none;
33 | -moz-appearance: none;
34 | -webkit-appearance: none;
35 | outline: none;
36 | cursor: pointer;
37 | }
38 |
39 | browser-appbar-history button:before {
40 | display: inline-block;
41 | width: 100%;
42 | font-family: 'icon';
43 | text-align: center;
44 | speak: none;
45 | -webkit-font-smoothing: antialiased;
46 | }
47 |
48 | browser-appbar-history button[disabled],
49 | browser-appbar-history button[disabled]:hover {
50 | color: var(--element-disable-color);
51 | border: 1px solid transparent;
52 | background: var(--element-disable-background);
53 | cursor: not-allowed;
54 | }
55 |
56 | browser-appbar-history button:focus,
57 | browser-appbar-history button:hover {
58 | color: var(--element-focus-color);
59 | border: 1px solid var(--element-focus-color);
60 | background: var(--element-focus-background);
61 | outline: none;
62 | }
63 |
64 | browser-appbar-history button::-moz-focus-inner {
65 | border: 0px solid transparent;
66 | }
67 |
68 | browser-appbar-history button[data-key="back"]:before { content: '\e5c4'; }
69 | browser-appbar-history button[data-key="next"]:before { content: '\e5c8'; }
70 | browser-appbar-history button[data-key="reload"]:before { content: '\e5d5'; }
71 | browser-appbar-history button[data-key="open"]:before { content: '\e145'; }
72 |
73 |
--------------------------------------------------------------------------------
/base/review/MODULE.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import BASE from '../../base/index.mjs';
4 |
5 |
6 |
7 | describe('MODULE', function(assert) {
8 |
9 | assert(typeof BASE['console'], 'object');
10 |
11 | assert(typeof BASE['Array'], 'function');
12 | assert(typeof BASE['Boolean'], 'function');
13 | assert(typeof BASE['Buffer'], 'function');
14 | assert(typeof BASE['Date'], 'function');
15 | assert(typeof BASE['Emitter'], 'function');
16 | assert(typeof BASE['Function'], 'function');
17 | assert(typeof BASE['Map'], 'function');
18 | assert(typeof BASE['Number'], 'function');
19 | assert(typeof BASE['Object'], 'function');
20 | assert(typeof BASE['RegExp'], 'function');
21 | assert(typeof BASE['Set'], 'function');
22 | assert(typeof BASE['String'], 'function');
23 |
24 | assert(typeof BASE['isArray'], 'function');
25 | assert(typeof BASE['isBoolean'], 'function');
26 | assert(typeof BASE['isBuffer'], 'function');
27 | assert(typeof BASE['isDate'], 'function');
28 | assert(typeof BASE['isEmitter'], 'function');
29 | assert(typeof BASE['isFunction'], 'function');
30 | assert(typeof BASE['isMap'], 'function');
31 | assert(typeof BASE['isNumber'], 'function');
32 | assert(typeof BASE['isObject'], 'function');
33 | assert(typeof BASE['isRegExp'], 'function');
34 | assert(typeof BASE['isSet'], 'function');
35 | assert(typeof BASE['isString'], 'function');
36 |
37 | assert(Object.keys(BASE), [
38 |
39 | 'console',
40 |
41 | 'Array',
42 | 'Boolean',
43 | 'Buffer',
44 | 'Date',
45 | 'Emitter',
46 | 'Function',
47 | 'Map',
48 | 'Number',
49 | 'Object',
50 | 'RegExp',
51 | 'Set',
52 | 'String',
53 |
54 | 'isArray',
55 | 'isBoolean',
56 | 'isBuffer',
57 | 'isDate',
58 | 'isEmitter',
59 | 'isFunction',
60 | 'isMap',
61 | 'isNumber',
62 | 'isObject',
63 | 'isRegExp',
64 | 'isSet',
65 | 'isString'
66 |
67 | ]);
68 |
69 | });
70 |
71 |
72 | export default finish('base/MODULE', {
73 | internet: false,
74 | network: false
75 | });
76 |
77 |
--------------------------------------------------------------------------------
/browser/design/Assistant.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { console, isObject, isString } from '../extern/base.mjs';
3 |
4 |
5 |
6 | let TIMEOUT = Date.now();
7 |
8 | const Assistant = function(settings) {
9 |
10 | settings = isObject(settings) ? settings : {};
11 |
12 |
13 | this.settings = Object.freeze(Object.assign({
14 | name: null,
15 | widget: null,
16 | events: {}
17 | }, settings));
18 |
19 |
20 | this.__state = {
21 | sounds: {},
22 | texts: {}
23 | };
24 |
25 |
26 | if (
27 | isString(this.settings.widget) === true
28 | && isObject(this.settings.events) === true
29 | ) {
30 |
31 | for (let name in this.settings.events) {
32 |
33 | let text = this.settings.events[name];
34 | let audio = new Audio('/browser/design/' + this.settings.widget + '.' + name + '.wav');
35 |
36 | this.__state.sounds[name] = audio;
37 | this.__state.texts[name] = text;
38 |
39 | }
40 |
41 | }
42 |
43 | };
44 |
45 | Assistant.prototype = {
46 |
47 | emit: function(event) {
48 |
49 | event = isString(event) ? event : null;
50 |
51 |
52 | if (event !== null) {
53 |
54 | let sound = this.__state.sounds[event] || null;
55 | let text = this.__state.texts[event] || null;
56 |
57 | let allowed = false;
58 | let browser = window.parent.BROWSER || null;
59 | if (browser !== null) {
60 |
61 | if (browser.settings['interface']['assistant'] === true) {
62 | allowed = true;
63 | }
64 |
65 | }
66 |
67 | if (allowed === true) {
68 |
69 | if (sound !== null) {
70 |
71 | if (Date.now() > TIMEOUT + 100) {
72 |
73 | try {
74 | sound.play().catch(() => {});
75 | } catch (err) {
76 | // Do Nothing
77 | }
78 |
79 | TIMEOUT = Date.now();
80 |
81 | }
82 |
83 | }
84 |
85 | if (text !== null) {
86 | console.log('Assistant: ' + text);
87 | }
88 |
89 | }
90 |
91 |
92 | return true;
93 |
94 | }
95 |
96 | return false;
97 |
98 | }
99 |
100 | };
101 |
102 |
103 | export { Assistant };
104 |
105 |
--------------------------------------------------------------------------------
/.github/workflows/covert-check.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Covert Check
3 |
4 | on: [push]
5 |
6 | jobs:
7 |
8 | check-base:
9 |
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: actions/setup-node@v1
15 | with:
16 | node-version: 14
17 | - run: npm install
18 | - run: node ./make.mjs build
19 | - run: |
20 | cd ./base
21 | node ../covert/covert.mjs check "base/*" --debug=true --report=../.github/workflows/covert-check-base
22 | - uses: actions/upload-artifact@v2
23 | with:
24 | name: Report for Base
25 | path: .github/workflows/covert-check-base.*
26 | - run: node .github/workflows/covert-check.mjs .github/workflows/covert-check-base.report
27 |
28 | check-covert:
29 |
30 | runs-on: ubuntu-latest
31 |
32 | steps:
33 | - uses: actions/checkout@v2
34 | - uses: actions/setup-node@v1
35 | with:
36 | node-version: 14
37 | - run: npm install
38 | - run: node ./make.mjs build
39 | - run: |
40 | cd ./covert
41 | node ../covert/covert.mjs check "covert/*" --debug=true --report=../.github/workflows/covert-check-covert
42 | - uses: actions/upload-artifact@v2
43 | with:
44 | name: Report for Covert
45 | path: .github/workflows/covert-check-covert.*
46 | - run: node .github/workflows/covert-check.mjs .github/workflows/covert-check-covert.report
47 |
48 | check-stealth:
49 |
50 | runs-on: ubuntu-latest
51 |
52 | steps:
53 | - uses: actions/checkout@v2
54 | - uses: actions/setup-node@v1
55 | with:
56 | node-version: 14
57 | - run: npm install
58 | - run: node ./make.mjs build
59 | - run: |
60 | cd ./stealth
61 | node ../covert/covert.mjs check "stealth/*" --debug=true --report=../.github/workflows/covert-check-stealth
62 | - uses: actions/upload-artifact@v2
63 | with:
64 | name: Report for Stealth
65 | path: .github/workflows/covert-check-stealth.*
66 | - run: node .github/workflows/covert-check.mjs .github/workflows/covert-check-stealth.report
67 |
68 |
--------------------------------------------------------------------------------
/base/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Base Library
3 |
4 | The Base Library is a polyfilling abstraction layer that allows to use the
5 | identical code bases among the Web Browser and node.js without having to
6 | integrate a third-party build toolchain or build workflow.
7 |
8 | The intention of this library is to have an ECMAScript Modules abstraction
9 | that allows to include otherwise globally defined data types via a single
10 | file, and allows cross-context and cross-sandbox serialization of data
11 | types, which otherwise wouldn't be possible.
12 |
13 | As data type instances in ECMAScript runtimes are instanciated per-sandbox,
14 | this library also includes `is(Datatype)` replacements for `typeof` and
15 | `instanceof`, and it is recommended to use the `[Symbol.toStringTag]` on
16 | the prototype of custom Function Templates to stay compatible.
17 |
18 |
19 | ## Usage
20 |
21 | The Base Library is using a simple [make.mjs](./make.mjs) script that
22 | generates the [/build](./build) folder.
23 |
24 | The `make.mjs` is called by other build scripts, too:
25 |
26 | - [Browser's make.mjs](../browser/make.mjs)
27 | - [Covert's make.mjs](../covert/make.mjs)
28 | - [Stealth's make.mjs](../stealth/make.mjs)
29 |
30 |
31 | ```bash
32 | cd /path/to/stealth;
33 |
34 | # Build the Base Library
35 | node ./base/make.mjs;
36 |
37 | # Afterwards, simply copy/paste the generated ESM module and use import syntax
38 | # cp ./base/build/browser.mjs ./to/a/browser/project/extern/base.mjs;
39 | # cp ./base/build/node.mjs ./to/a/node/project/extern/base.mjs;
40 | ```
41 |
42 |
43 | ## ECMAScript Usage
44 |
45 | The Base Library exports a `default` export that contains an `Object` with
46 | all exports. Additionally, all methods and data types are exported as named
47 | exports.
48 |
49 | ```javascript
50 | // Import Base Library
51 | import base from './base/index.mjs';
52 |
53 | // Import selected named exports
54 | import { console, Emitter, isEmitter } from './base/index.mjs';
55 | ```
56 |
57 |
58 | ## NPM Usage
59 |
60 | ```javascript
61 | // Import Base Library
62 | import base from 'stealth/base';
63 |
64 | // Import selected named exports
65 | import { console, Emitter, isEmitter } from 'stealth/base';
66 | ```
67 |
68 |
--------------------------------------------------------------------------------
/stealth/source/client/service/Settings.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Emitter, isFunction, isObject } from '../../../extern/base.mjs';
3 |
4 |
5 |
6 | const Settings = function(client) {
7 |
8 | this.client = client;
9 | Emitter.call(this);
10 |
11 | };
12 |
13 |
14 | Settings.prototype = Object.assign({}, Emitter.prototype, {
15 |
16 | toJSON: function() {
17 |
18 | let blob = Emitter.prototype.toJSON.call(this);
19 | let data = {
20 | events: blob.data.events,
21 | journal: blob.data.journal
22 | };
23 |
24 | return {
25 | 'type': 'Settings Service',
26 | 'data': data
27 | };
28 |
29 | },
30 |
31 | info: function(payload, callback) {
32 |
33 | payload = isObject(payload) ? payload : null;
34 | callback = isFunction(callback) ? callback : null;
35 |
36 |
37 | if (callback !== null) {
38 |
39 | this.once('info', (response) => callback(response));
40 |
41 | this.client.send({
42 | headers: {
43 | service: 'settings',
44 | method: 'info'
45 | },
46 | payload: payload
47 | });
48 |
49 | }
50 |
51 | },
52 |
53 | read: function(payload, callback) {
54 |
55 | payload = isObject(payload) ? payload : null;
56 | callback = isFunction(callback) ? callback : null;
57 |
58 |
59 | if (callback !== null) {
60 |
61 | this.once('read', (response) => callback(response));
62 |
63 | this.client.send({
64 | headers: {
65 | service: 'settings',
66 | method: 'read'
67 | },
68 | payload: payload
69 | });
70 |
71 | }
72 |
73 | },
74 |
75 | save: function(payload, callback) {
76 |
77 | payload = isObject(payload) ? payload : null;
78 | callback = isFunction(callback) ? callback : null;
79 |
80 |
81 | if (payload !== null && callback !== null) {
82 |
83 | this.once('save', (result) => callback(result));
84 |
85 | this.client.send({
86 | headers: {
87 | service: 'settings',
88 | method: 'save'
89 | },
90 | payload: payload
91 | });
92 |
93 | } else if (callback !== null) {
94 | callback(false);
95 | }
96 |
97 | }
98 |
99 | });
100 |
101 |
102 | export { Settings };
103 |
104 |
--------------------------------------------------------------------------------
/guide/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Developer Guide
3 |
4 | This Guide is intended as a reference document for both the concept and architecture,
5 | and their documentation of implementations.
6 |
7 | The idea is that this guide helps developers to implement their own Browser Frontend
8 | (or Client or Peer) that can reuse the Stealth Service and its provided APIs.
9 |
10 | Currently, the Repository consists of these important folders:
11 |
12 | - [Base](/base) is a Library that contains Polyfills for Browser, Covert and Stealth.
13 | - [Browser](/browser) is the Web-based Frontend for Stealth that can be used as a Progressive Web App.
14 | - [Covert](/covert) is the Test Runner.
15 | - [Profile](/profile) is the Tholian Vendor Profile.
16 | - [Stealth](/stealth) is the Stealth Service (that runs in node.js).
17 |
18 |
19 | # Guide Contents
20 |
21 | - [README.md](./README.md) is this file.
22 | - [security/Network.md](./security/Network.md) contains the Network Security guide and explains potential Attack Vectors.
23 | - [security/Site.md](./security/Site.md) contains the Site Security guide and explains potential Attack Vectors.
24 | - [concept/Browser.md](./concept/Browser.md) explains the Browser and Browser UI.
25 | - [concept/Covert.md](./concept/Covert.md) explains the Covert Testrunner.
26 | - [concept/Stealth.md](./concept/Stealth.md) explains the Stealth Service.
27 | - [concept/Service.md](./concept/Service.md) explains the Stealth Service API.
28 | - [concept/Network.md](./concept/Network.md) explains Network Topology and Service Discovery Workflows.
29 |
30 |
31 | # Implementation Notes
32 |
33 | - [implementation/Parser.md](./implementation/Parser.md) documents parser implementation quirks and differences.
34 |
35 |
36 | # Implementation Examples
37 |
38 | Each Project has a `/review` folder which contains Reviews for Covert. The idea behind a
39 | `Review` is the complete audit-by-example of the equivalent implementation in the `/source`
40 | folder.
41 |
42 | For example, the [/stealth/review/Request.mjs](/stealth/review/Request.mjs) reviews the
43 | [/stealth/source/Request.mjs](/stealth/source/Request.mjs) implementation and it can be
44 | run directly with `covert scan stealth/Request`.
45 |
46 | The Usage of `Covert` is documented in the Covert's [README.md](/covert/README.md) file.
47 |
48 |
--------------------------------------------------------------------------------
/browser/design/card/Blocker.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Element } from '../Element.mjs';
3 | import { Widget } from '../Widget.mjs';
4 | import { isArray, isObject } from '../../extern/base.mjs';
5 |
6 |
7 |
8 | const Blocker = function(browser, actions) {
9 |
10 | this.actions = isArray(actions) ? actions : [];
11 | this.element = new Element('browser-card-blocker', [
12 | ' ',
13 | ' ',
14 | '',
15 | ' ',
16 | '',
17 | ' '
18 | ]);
19 |
20 | this.buttons = {
21 | toggle: this.element.query('button[data-action="toggle"]')
22 | };
23 |
24 | this.model = {
25 | domain: this.element.query('[data-key="domain"]')
26 | };
27 |
28 | Widget.call(this);
29 |
30 |
31 | this.element.on('show', () => {
32 |
33 | this.element.state('active');
34 |
35 | if (this.buttons.toggle !== null) {
36 | this.buttons.toggle.state('active');
37 | }
38 |
39 | });
40 |
41 | this.element.on('hide', () => {
42 |
43 | this.element.state('');
44 |
45 | if (this.buttons.toggle !== null) {
46 | this.buttons.toggle.state('');
47 | }
48 |
49 | });
50 |
51 | this.element.on('update', () => {
52 | // Ignore
53 | });
54 |
55 |
56 | if (this.buttons.toggle !== null) {
57 |
58 | this.buttons.toggle.on('click', () => {
59 |
60 | if (this.element.state() === 'active') {
61 | this.element.emit('hide');
62 | } else {
63 | this.element.emit('show');
64 | }
65 |
66 | });
67 |
68 | }
69 |
70 | this.element.emit('update');
71 |
72 | };
73 |
74 |
75 | Blocker.from = function(value, actions) {
76 |
77 | value = isObject(value) ? value : null;
78 | actions = isArray(actions) ? actions : null;
79 |
80 |
81 | let widget = null;
82 |
83 | if (value !== null) {
84 |
85 | widget = new Blocker(window.parent.BROWSER || null, actions);
86 | widget.value(value);
87 |
88 | }
89 |
90 | return widget;
91 |
92 | };
93 |
94 |
95 | Blocker.prototype = Object.assign({}, Widget.prototype);
96 |
97 |
98 | export { Blocker };
99 |
100 |
--------------------------------------------------------------------------------
/browser/internal/media/Media.css:
--------------------------------------------------------------------------------
1 |
2 | @import url("../../design/widget/Image.css");
3 | @import url("../../design/widget/Audio.css");
4 | @import url("../../design/widget/Video.css");
5 |
6 |
7 |
8 | browser-widget-media {
9 | all: unset;
10 | display: block;
11 | position: relative;
12 | width: 800px;
13 | margin: 0px;
14 | padding: 16px;
15 | background: var(--surface-default-background);
16 | border: 1px solid var(--surface-default-color);
17 | border-radius: 8px;
18 | box-sizing: border-box;
19 | transition: 200ms all ease-out;
20 | overflow: hidden;
21 | cursor: default;
22 | }
23 |
24 | browser-widget-media > h3 {
25 | display: block;
26 | margin: 0px 0px 16px 0px;
27 | padding: 0px;
28 | font-family: 'crystalline';
29 | font-size: 32px;
30 | font-weight: 500;
31 | line-height: 32px;
32 | text-transform: uppercase;
33 | border-bottom: 1px solid var(--surface-default-color);
34 | }
35 |
36 | browser-widget-media > h3:before {
37 | display: inline;
38 | content: '\e038';
39 | margin: 0px 16px 0px 0px;
40 | padding: 0px;
41 | font-family: 'icon';
42 | font-size: 24px;
43 | font-weight: 400;
44 | text-align: center;
45 | vertical-align: bottom;
46 | speak: none;
47 | -webkit-font-smoothing: antialiased;
48 | }
49 |
50 |
51 |
52 | browser-widget-media-header {
53 | display: block;
54 | width: 100%;
55 | height: auto;
56 | }
57 |
58 | browser-widget-media-header input[type="text"] {
59 | display: block;
60 | width: 100%;
61 | margin: 0px auto 16px auto;
62 | text-align: center;
63 | }
64 |
65 |
66 |
67 | browser-widget-media-article {
68 | display: block;
69 | width: 100%;
70 | height: auto;
71 | }
72 |
73 | browser-widget-media-article > * {
74 | width: 100% !important;
75 | margin: 0px 0px 16px 0px !important;
76 | }
77 |
78 |
79 |
80 | browser-widget-media-footer {
81 | display: block;
82 | height: 32px;
83 | margin: 16px 0px 0px 0px;
84 | padding: 0px;
85 | text-align: right;
86 | }
87 |
88 | browser-widget-media-footer > button {
89 | margin-left: 16px;
90 | }
91 |
92 | browser-widget-media-footer > button[data-action="refresh"]:before { content: '\e5d5'; }
93 |
94 |
95 |
96 | @media screen and (max-width: 800px) {
97 |
98 | browser-widget-media {
99 | width: auto;
100 | }
101 |
102 | }
103 |
104 |
--------------------------------------------------------------------------------
/stealth/source/client/service/Echo.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Emitter, isFunction, isObject } from '../../../extern/base.mjs';
3 |
4 |
5 |
6 | const Echo = function(client) {
7 |
8 | this.client = client;
9 | Emitter.call(this);
10 |
11 | };
12 |
13 |
14 | Echo.prototype = Object.assign({}, Emitter.prototype, {
15 |
16 | toJSON: function() {
17 |
18 | let blob = Emitter.prototype.toJSON.call(this);
19 | let data = {
20 | events: blob.data.events,
21 | journal: blob.data.journal
22 | };
23 |
24 | return {
25 | 'type': 'Echo Service',
26 | 'data': data
27 | };
28 |
29 | },
30 |
31 | read: function(payload, callback) {
32 |
33 | payload = isObject(payload) ? payload : null;
34 | callback = isFunction(callback) ? callback : null;
35 |
36 |
37 | if (payload !== null && callback !== null) {
38 |
39 | this.once('read', (response) => callback(response));
40 |
41 | this.client.send({
42 | headers: {
43 | service: 'echo',
44 | method: 'read'
45 | },
46 | payload: payload
47 | });
48 |
49 | } else if (callback !== null) {
50 | callback(null);
51 | }
52 |
53 | },
54 |
55 | remove: function(payload, callback) {
56 |
57 | payload = isObject(payload) ? payload : null;
58 | callback = isFunction(callback) ? callback : null;
59 |
60 |
61 | if (payload !== null && callback !== null) {
62 |
63 | this.once('remove', (result) => callback(result));
64 |
65 | this.client.send({
66 | headers: {
67 | service: 'echo',
68 | method: 'remove'
69 | },
70 | payload: payload
71 | });
72 |
73 | } else if (callback !== null) {
74 | callback(false);
75 | }
76 |
77 | },
78 |
79 | save: function(payload, callback) {
80 |
81 | payload = isObject(payload) ? payload : null;
82 | callback = isFunction(callback) ? callback : null;
83 |
84 |
85 | if (payload !== null && callback !== null) {
86 |
87 | this.once('save', (result) => callback(result));
88 |
89 | this.client.send({
90 | headers: {
91 | service: 'echo',
92 | method: 'save'
93 | },
94 | payload: payload
95 | });
96 |
97 | } else if (callback !== null) {
98 | callback(false);
99 | }
100 |
101 | }
102 |
103 | });
104 |
105 |
106 | export { Echo };
107 |
108 |
--------------------------------------------------------------------------------
/stealth/source/client/service/Mode.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Emitter, isFunction, isObject } from '../../../extern/base.mjs';
3 |
4 |
5 |
6 | const Mode = function(client) {
7 |
8 | this.client = client;
9 | Emitter.call(this);
10 |
11 | };
12 |
13 |
14 | Mode.prototype = Object.assign({}, Emitter.prototype, {
15 |
16 | toJSON: function() {
17 |
18 | let blob = Emitter.prototype.toJSON.call(this);
19 | let data = {
20 | events: blob.data.events,
21 | journal: blob.data.journal
22 | };
23 |
24 | return {
25 | 'type': 'Mode Service',
26 | 'data': data
27 | };
28 |
29 | },
30 |
31 | read: function(payload, callback) {
32 |
33 | payload = isObject(payload) ? payload : null;
34 | callback = isFunction(callback) ? callback : null;
35 |
36 |
37 | if (payload !== null && callback !== null) {
38 |
39 | this.once('read', (response) => callback(response));
40 |
41 | this.client.send({
42 | headers: {
43 | service: 'mode',
44 | method: 'read'
45 | },
46 | payload: payload
47 | });
48 |
49 | } else if (callback !== null) {
50 | callback(null);
51 | }
52 |
53 | },
54 |
55 | remove: function(payload, callback) {
56 |
57 | payload = isObject(payload) ? payload : null;
58 | callback = isFunction(callback) ? callback : null;
59 |
60 |
61 | if (payload !== null && callback !== null) {
62 |
63 | this.once('remove', (result) => callback(result));
64 |
65 | this.client.send({
66 | headers: {
67 | service: 'mode',
68 | method: 'remove'
69 | },
70 | payload: payload
71 | });
72 |
73 | } else if (callback !== null) {
74 | callback(false);
75 | }
76 |
77 | },
78 |
79 | save: function(payload, callback) {
80 |
81 | payload = isObject(payload) ? payload : null;
82 | callback = isFunction(callback) ? callback : null;
83 |
84 |
85 | if (payload !== null && callback !== null) {
86 |
87 | this.once('save', (result) => callback(result));
88 |
89 | this.client.send({
90 | headers: {
91 | service: 'mode',
92 | method: 'save'
93 | },
94 | payload: payload
95 | });
96 |
97 | } else if (callback !== null) {
98 | callback(false);
99 | }
100 |
101 | }
102 |
103 | });
104 |
105 |
106 | export { Mode };
107 |
108 |
--------------------------------------------------------------------------------
/stealth/source/client/service/Task.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Emitter, isFunction, isObject } from '../../../extern/base.mjs';
3 |
4 |
5 |
6 | const Task = function(client) {
7 |
8 | this.client = client;
9 | Emitter.call(this);
10 |
11 | };
12 |
13 |
14 | Task.prototype = Object.assign({}, Emitter.prototype, {
15 |
16 | toJSON: function() {
17 |
18 | let blob = Emitter.prototype.toJSON.call(this);
19 | let data = {
20 | events: blob.data.events,
21 | journal: blob.data.journal
22 | };
23 |
24 | return {
25 | 'type': 'Task Service',
26 | 'data': data
27 | };
28 |
29 | },
30 |
31 | read: function(payload, callback) {
32 |
33 | payload = isObject(payload) ? payload : null;
34 | callback = isFunction(callback) ? callback : null;
35 |
36 |
37 | if (payload !== null && callback !== null) {
38 |
39 | this.once('read', (response) => callback(response));
40 |
41 | this.client.send({
42 | headers: {
43 | service: 'task',
44 | method: 'read'
45 | },
46 | payload: payload
47 | });
48 |
49 | } else if (callback !== null) {
50 | callback(null);
51 | }
52 |
53 | },
54 |
55 | remove: function(payload, callback) {
56 |
57 | payload = isObject(payload) ? payload : null;
58 | callback = isFunction(callback) ? callback : null;
59 |
60 |
61 | if (payload !== null && callback !== null) {
62 |
63 | this.once('remove', (result) => callback(result));
64 |
65 | this.client.send({
66 | headers: {
67 | service: 'task',
68 | method: 'remove'
69 | },
70 | payload: payload
71 | });
72 |
73 | } else if (callback !== null) {
74 | callback(false);
75 | }
76 |
77 | },
78 |
79 | save: function(payload, callback) {
80 |
81 | payload = isObject(payload) ? payload : null;
82 | callback = isFunction(callback) ? callback : null;
83 |
84 |
85 | if (payload !== null && callback !== null) {
86 |
87 | this.once('save', (result) => callback(result));
88 |
89 | this.client.send({
90 | headers: {
91 | service: 'task',
92 | method: 'save'
93 | },
94 | payload: payload
95 | });
96 |
97 | } else if (callback !== null) {
98 | callback(false);
99 | }
100 |
101 | }
102 |
103 | });
104 |
105 |
106 | export { Task };
107 |
108 |
--------------------------------------------------------------------------------
/stealth/source/client/service/Beacon.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Emitter, isFunction, isObject } from '../../../extern/base.mjs';
3 |
4 |
5 |
6 | const Beacon = function(client) {
7 |
8 | this.client = client;
9 | Emitter.call(this);
10 |
11 | };
12 |
13 |
14 | Beacon.prototype = Object.assign({}, Emitter.prototype, {
15 |
16 | toJSON: function() {
17 |
18 | let blob = Emitter.prototype.toJSON.call(this);
19 | let data = {
20 | events: blob.data.events,
21 | journal: blob.data.journal
22 | };
23 |
24 | return {
25 | 'type': 'Beacon Service',
26 | 'data': data
27 | };
28 |
29 | },
30 |
31 | read: function(payload, callback) {
32 |
33 | payload = isObject(payload) ? payload : null;
34 | callback = isFunction(callback) ? callback : null;
35 |
36 |
37 | if (payload !== null && callback !== null) {
38 |
39 | this.once('read', (response) => callback(response));
40 |
41 | this.client.send({
42 | headers: {
43 | service: 'beacon',
44 | method: 'read'
45 | },
46 | payload: payload
47 | });
48 |
49 | } else if (callback !== null) {
50 | callback(null);
51 | }
52 |
53 | },
54 |
55 | remove: function(payload, callback) {
56 |
57 | payload = isObject(payload) ? payload : null;
58 | callback = isFunction(callback) ? callback : null;
59 |
60 |
61 | if (payload !== null && callback !== null) {
62 |
63 | this.once('remove', (result) => callback(result));
64 |
65 | this.client.send({
66 | headers: {
67 | service: 'beacon',
68 | method: 'remove'
69 | },
70 | payload: payload
71 | });
72 |
73 | } else if (callback !== null) {
74 | callback(false);
75 | }
76 |
77 | },
78 |
79 | save: function(payload, callback) {
80 |
81 | payload = isObject(payload) ? payload : null;
82 | callback = isFunction(callback) ? callback : null;
83 |
84 |
85 | if (payload !== null && callback !== null) {
86 |
87 | this.once('save', (result) => callback(result));
88 |
89 | this.client.send({
90 | headers: {
91 | service: 'beacon',
92 | method: 'save'
93 | },
94 | payload: payload
95 | });
96 |
97 | } else if (callback !== null) {
98 | callback(false);
99 | }
100 |
101 | }
102 |
103 | });
104 |
105 |
106 | export { Beacon };
107 |
108 |
--------------------------------------------------------------------------------
/stealth/source/client/service/Policy.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Emitter, isFunction, isObject } from '../../../extern/base.mjs';
3 |
4 |
5 |
6 | const Policy = function(client) {
7 |
8 | this.client = client;
9 | Emitter.call(this);
10 |
11 | };
12 |
13 |
14 | Policy.prototype = Object.assign({}, Emitter.prototype, {
15 |
16 | toJSON: function() {
17 |
18 | let blob = Emitter.prototype.toJSON.call(this);
19 | let data = {
20 | events: blob.data.events,
21 | journal: blob.data.journal
22 | };
23 |
24 | return {
25 | 'type': 'Policy Service',
26 | 'data': data
27 | };
28 |
29 | },
30 |
31 | read: function(payload, callback) {
32 |
33 | payload = isObject(payload) ? payload : null;
34 | callback = isFunction(callback) ? callback : null;
35 |
36 |
37 | if (payload !== null && callback !== null) {
38 |
39 | this.once('read', (response) => callback(response));
40 |
41 | this.client.send({
42 | headers: {
43 | service: 'policy',
44 | method: 'read'
45 | },
46 | payload: payload
47 | });
48 |
49 | } else if (callback !== null) {
50 | callback(null);
51 | }
52 |
53 | },
54 |
55 | remove: function(payload, callback) {
56 |
57 | payload = isObject(payload) ? payload : null;
58 | callback = isFunction(callback) ? callback : null;
59 |
60 |
61 | if (payload !== null && callback !== null) {
62 |
63 | this.once('remove', (result) => callback(result));
64 |
65 | this.client.send({
66 | headers: {
67 | service: 'policy',
68 | method: 'remove'
69 | },
70 | payload: payload
71 | });
72 |
73 | } else if (callback !== null) {
74 | callback(false);
75 | }
76 |
77 | },
78 |
79 | save: function(payload, callback) {
80 |
81 | payload = isObject(payload) ? payload : null;
82 | callback = isFunction(callback) ? callback : null;
83 |
84 |
85 | if (payload !== null && callback !== null) {
86 |
87 | this.once('save', (result) => callback(result));
88 |
89 | this.client.send({
90 | headers: {
91 | service: 'policy',
92 | method: 'save'
93 | },
94 | payload: payload
95 | });
96 |
97 | } else if (callback !== null) {
98 | callback(false);
99 | }
100 |
101 | }
102 |
103 | });
104 |
105 |
106 | export { Policy };
107 |
108 |
--------------------------------------------------------------------------------
/stealth/review/client/service/Blocker.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { isFunction } from '../../../../base/index.mjs';
3 | import { after, before, describe, finish } from '../../../../covert/index.mjs';
4 | import { Blocker } from '../../../../stealth/source/client/service/Blocker.mjs';
5 | import { connect as connect_stealth, disconnect as disconnect_stealth } from '../../../../stealth/review/Stealth.mjs';
6 | import { connect as connect_client, disconnect as disconnect_client } from '../../../../stealth/review/Client.mjs';
7 |
8 |
9 |
10 | before(connect_stealth);
11 | before(connect_client);
12 |
13 | describe('new Blocker()', function(assert) {
14 |
15 | assert(this.client !== null);
16 | assert(this.client.services.blocker instanceof Blocker, true);
17 |
18 | });
19 |
20 | describe('Blocker.prototype.toJSON()', function(assert) {
21 |
22 | assert(this.client !== null);
23 | assert(isFunction(this.client.services.blocker.toJSON), true);
24 |
25 | assert(this.client.services.blocker.toJSON(), {
26 | type: 'Blocker Service',
27 | data: {
28 | events: [],
29 | journal: []
30 | }
31 | });
32 |
33 | });
34 |
35 | describe('Blocker.prototype.read()/success', function(assert) {
36 |
37 | assert(this.client !== null);
38 | assert(isFunction(this.client.services.blocker.read), true);
39 |
40 | this.client.services.blocker.read({
41 | domain: 'ads.quantserve.com'
42 | }, (response) => {
43 |
44 | assert(response, {
45 | domain: 'ads.quantserve.com'
46 | });
47 |
48 | });
49 |
50 | this.client.services.blocker.read({
51 | domain: 'subdomain.ads.quantserve.com'
52 | }, (response) => {
53 |
54 | assert(response, {
55 | domain: 'ads.quantserve.com'
56 | });
57 |
58 | });
59 |
60 | });
61 |
62 | describe('Blocker.prototype.read()/failure', function(assert) {
63 |
64 | assert(this.client !== null);
65 | assert(isFunction(this.client.services.blocker.read), true);
66 |
67 | this.client.services.blocker.read({
68 | domain: 'quantserve.com'
69 | }, (response) => {
70 |
71 | assert(response, null);
72 |
73 | });
74 |
75 | });
76 |
77 | after(disconnect_client);
78 | after(disconnect_stealth);
79 |
80 |
81 | export default finish('stealth/client/service/Blocker', {
82 | internet: false,
83 | network: true
84 | });
85 |
86 |
--------------------------------------------------------------------------------
/stealth/source/client/service/Redirect.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Emitter, isFunction, isObject } from '../../../extern/base.mjs';
3 |
4 |
5 |
6 | const Redirect = function(client) {
7 |
8 | this.client = client;
9 | Emitter.call(this);
10 |
11 | };
12 |
13 |
14 | Redirect.prototype = Object.assign({}, Emitter.prototype, {
15 |
16 | toJSON: function() {
17 |
18 | let blob = Emitter.prototype.toJSON.call(this);
19 | let data = {
20 | events: blob.data.events,
21 | journal: blob.data.journal
22 | };
23 |
24 | return {
25 | 'type': 'Redirect Service',
26 | 'data': data
27 | };
28 |
29 | },
30 |
31 | read: function(payload, callback) {
32 |
33 | payload = isObject(payload) ? payload : null;
34 | callback = isFunction(callback) ? callback : null;
35 |
36 |
37 | if (payload !== null && callback !== null) {
38 |
39 | this.once('read', (response) => callback(response));
40 |
41 | this.client.send({
42 | headers: {
43 | service: 'redirect',
44 | method: 'read'
45 | },
46 | payload: payload
47 | });
48 |
49 | } else if (callback !== null) {
50 | callback(null);
51 | }
52 |
53 | },
54 |
55 | remove: function(payload, callback) {
56 |
57 | payload = isObject(payload) ? payload : null;
58 | callback = isFunction(callback) ? callback : null;
59 |
60 |
61 | if (payload !== null && callback !== null) {
62 |
63 | this.once('remove', (result) => callback(result));
64 |
65 | this.client.send({
66 | headers: {
67 | service: 'redirect',
68 | method: 'remove'
69 | },
70 | payload: payload
71 | });
72 |
73 | } else if (callback !== null) {
74 | callback(false);
75 | }
76 |
77 | },
78 |
79 | save: function(payload, callback) {
80 |
81 | payload = isObject(payload) ? payload : null;
82 | callback = isFunction(callback) ? callback : null;
83 |
84 |
85 | if (payload !== null && callback !== null) {
86 |
87 | this.once('save', (result) => callback(result));
88 |
89 | this.client.send({
90 | headers: {
91 | service: 'redirect',
92 | method: 'save'
93 | },
94 | payload: payload
95 | });
96 |
97 | } else if (callback !== null) {
98 | callback(false);
99 | }
100 |
101 | }
102 |
103 | });
104 |
105 |
106 | export { Redirect };
107 |
108 |
--------------------------------------------------------------------------------
/browser/design/appbar/Settings.css:
--------------------------------------------------------------------------------
1 |
2 | browser-appbar-settings {
3 | display: inline-block;
4 | position: relative;
5 | width: auto;
6 | height: 32px;
7 | margin: 0px 0px 0px 32px;
8 | padding: 0px;
9 | line-height: 32px;
10 | text-align: center;
11 | vertical-align: top;
12 | white-space: nowrap;
13 | user-select: none;
14 | -webkit-user-select: none;
15 | }
16 |
17 | browser-appbar-settings button {
18 | display: inline-block;
19 | min-width: 32px;
20 | margin: 0px;
21 | padding: 0px;
22 | line-height: 32px;
23 | height: 32px;
24 | color: var(--element-default-color);
25 | font-size: 24px;
26 | text-align: center;
27 | background: var(--element-default-background);
28 | border: 1px solid transparent;
29 | border-radius: 4px;
30 | box-sizing: border-box;
31 | transition: 200ms all ease-out;
32 | appearance: none;
33 | -moz-appearance: none;
34 | -webkit-appearance: none;
35 | outline: none;
36 | cursor: pointer;
37 | }
38 |
39 | browser-appbar-settings button:before {
40 | display: inline-block;
41 | width: 100%;
42 | font-family: 'icon';
43 | text-align: center;
44 | speak: none;
45 | -webkit-font-smoothing: antialiased;
46 | }
47 |
48 | browser-appbar-settings button.active {
49 | color: var(--element-active-color);
50 | }
51 |
52 | browser-appbar-settings button[disabled],
53 | browser-appbar-settings button[disabled]:hover {
54 | color: var(--element-disable-color);
55 | border: 1px solid transparent;
56 | background: var(--element-disable-background);
57 | cursor: not-allowed;
58 | }
59 |
60 | browser-appbar-settings button[disabled].active {
61 | color: var(--element-active-color);
62 | }
63 |
64 | browser-appbar-settings button:focus,
65 | browser-appbar-settings button:hover {
66 | color: var(--element-focus-color);
67 | border: 1px solid var(--element-focus-color);
68 | background: var(--element-focus-background);
69 | outline: none;
70 | }
71 |
72 | browser-appbar-settings button::-moz-focus-inner {
73 | border: 0px solid transparent;
74 | }
75 |
76 | browser-appbar-settings button[data-key="session"]:before { content: '\e1e2'; }
77 | browser-appbar-settings button[data-key="site"]:before { content: '\e869'; }
78 | browser-appbar-settings button[data-key="browser"]:before { content: '\e8b8'; }
79 |
80 |
81 |
82 | @media screen and (max-width: 800px) {
83 |
84 | browser-appbar-settings {
85 | margin-left: 0px;
86 | }
87 |
88 | }
89 |
90 |
--------------------------------------------------------------------------------
/browser/design/card/Blocker.css:
--------------------------------------------------------------------------------
1 |
2 | browser-card-blocker {
3 | all: unset;
4 | display: block;
5 | position: relative;
6 | width: 800px;
7 | max-height: 64px;
8 | margin: 0px;
9 | padding: 16px;
10 | background: var(--surface-default-background);
11 | border: 1px solid var(--surface-default-color);
12 | border-radius: 8px;
13 | box-sizing: border-box;
14 | transition: 200ms all ease-out;
15 | overflow: hidden;
16 | cursor: default;
17 | }
18 |
19 | browser-card-blocker.active {
20 | max-height: unset;
21 | }
22 |
23 | browser-card-blocker > h3 {
24 | display: block;
25 | margin: 0px 0px 16px 0px;
26 | padding: 0px;
27 | }
28 |
29 | browser-card-blocker > h3:before {
30 | display: inline;
31 | content: '\e32a';
32 | margin: 0px 16px 0px 0px;
33 | padding: 0px;
34 | font-family: 'icon';
35 | font-size: 24px;
36 | font-weight: 400;
37 | text-align: center;
38 | vertical-align: middle;
39 | speak: none;
40 | -webkit-font-smoothing: antialiased;
41 | }
42 |
43 | browser-card-blocker > h3 input[type="text"] {
44 | display: inline-block;
45 | width: calc(100% - 32px - 32px - 24px);
46 | font-family: 'vera-mono';
47 | font-size: 16px;
48 | font-weight: 400;
49 | line-height: 32px;
50 | }
51 |
52 | browser-card-blocker > button[data-action="toggle"] {
53 | display: block;
54 | position: absolute;
55 | top: 16px;
56 | right: 16px;
57 | bottom: auto;
58 | left: auto;
59 | }
60 |
61 | browser-card-blocker > button[data-action="toggle"]:before {
62 | content: '\e5c5';
63 | transition: 200ms transform ease-out;
64 | }
65 |
66 | browser-card-blocker > button[data-action="toggle"].active:before {
67 | transform: rotate(180deg);
68 | transition: 200ms transform ease-out;
69 | }
70 |
71 |
72 |
73 | browser-card-blocker-article {
74 | display: none;
75 | width: 100%;
76 | height: auto;
77 | }
78 |
79 | browser-card-blocker.active browser-card-blocker-article {
80 | display: block;
81 | }
82 |
83 |
84 |
85 | browser-card-blocker-footer {
86 | display: none;
87 | height: 32px;
88 | margin: 16px 0px 0px 0px;
89 | padding: 0px;
90 | text-align: right;
91 | }
92 |
93 | browser-card-blocker.active browser-card-blocker-footer {
94 | display: block;
95 | }
96 |
97 |
98 |
99 | @media screen and (max-width: 800px) {
100 |
101 | browser-card-blocker {
102 | width: auto;
103 | }
104 |
105 | }
106 |
107 |
--------------------------------------------------------------------------------
/browser/design/appbar/Mode.css:
--------------------------------------------------------------------------------
1 |
2 | browser-appbar-mode {
3 | display: inline-block;
4 | position: relative;
5 | width: auto;
6 | height: 32px;
7 | margin: 0px;
8 | padding: 0px;
9 | line-height: 32px;
10 | text-align: center;
11 | vertical-align: top;
12 | white-space: nowrap;
13 | user-select: none;
14 | -webkit-user-select: none;
15 | }
16 |
17 | browser-appbar-mode button {
18 | display: inline-block;
19 | min-width: 32px;
20 | margin: 0px;
21 | padding: 0px;
22 | line-height: 32px;
23 | height: 32px;
24 | color: var(--element-default-color);
25 | font-size: 24px;
26 | text-align: center;
27 | background: var(--element-default-background);
28 | border: 1px solid transparent;
29 | border-radius: 4px;
30 | box-sizing: border-box;
31 | transition: 200ms all ease-out;
32 | appearance: none;
33 | -moz-appearance: none;
34 | -webkit-appearance: none;
35 | outline: none;
36 | cursor: pointer;
37 | }
38 |
39 | browser-appbar-mode button:before {
40 | display: inline-block;
41 | width: 100%;
42 | font-family: 'icon';
43 | text-align: center;
44 | speak: none;
45 | -webkit-font-smoothing: antialiased;
46 | }
47 |
48 | browser-appbar-mode button[disabled],
49 | browser-appbar-mode button[disabled]:hover {
50 | color: var(--element-disable-color);
51 | border: 1px solid transparent;
52 | background: var(--element-disable-background);
53 | cursor: not-allowed;
54 | }
55 |
56 | browser-appbar-mode button:focus,
57 | browser-appbar-mode button:hover {
58 | color: var(--element-focus-color);
59 | border: 1px solid var(--element-focus-color);
60 | background: var(--element-focus-background);
61 | outline: none;
62 | }
63 |
64 | browser-appbar-mode button::-moz-focus-inner {
65 | border: 0px solid transparent;
66 | }
67 |
68 | browser-appbar-mode input[data-key="domain"] {
69 | display: none;
70 | }
71 |
72 | browser-appbar-mode button[data-key="mode.text"]:before { content: '\e06f'; }
73 | browser-appbar-mode button[data-key="mode.image"]:before { content: '\e432'; }
74 | browser-appbar-mode button[data-key="mode.audio"]:before { content: '\e049'; }
75 | browser-appbar-mode button[data-key="mode.video"]:before { content: '\e404'; }
76 | browser-appbar-mode button[data-key="mode.other"]:before { content: '\e048'; }
77 | browser-appbar-mode button[data-val="true"] { color: var(--element-active-color); }
78 | browser-appbar-mode button[data-val="true"]:hover { color: var(--element-active-color); }
79 |
80 |
--------------------------------------------------------------------------------
/base/review/Boolean.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import { Boolean, isBoolean } from '../source/Boolean.mjs';
4 |
5 |
6 |
7 | describe('Boolean.isBoolean()', function(assert) {
8 |
9 | let boolean1 = true;
10 | let boolean2 = false;
11 | let boolean3 = new Boolean(true);
12 | let boolean4 = new Boolean(false);
13 |
14 | assert(typeof Boolean.isBoolean, 'function');
15 |
16 | assert(Boolean.isBoolean(boolean1), true);
17 | assert(Boolean.isBoolean(boolean2), true);
18 | assert(Boolean.isBoolean(boolean3), true);
19 | assert(Boolean.isBoolean(boolean4), true);
20 |
21 | });
22 |
23 | describe('isBoolean()', function(assert) {
24 |
25 | let boolean1 = true;
26 | let boolean2 = false;
27 | let boolean3 = new Boolean(true);
28 | let boolean4 = new Boolean(false);
29 |
30 | assert(typeof isBoolean, 'function');
31 |
32 | assert(isBoolean(boolean1), true);
33 | assert(isBoolean(boolean2), true);
34 | assert(isBoolean(boolean3), true);
35 | assert(isBoolean(boolean4), true);
36 |
37 | });
38 |
39 | describe('Boolean.prototype.toString()', function(assert) {
40 |
41 | let boolean1 = true;
42 | let boolean2 = false;
43 | let boolean3 = new Boolean(true);
44 | let boolean4 = new Boolean(false);
45 |
46 | assert(Object.prototype.toString.call(boolean1), '[object Boolean]');
47 | assert(Object.prototype.toString.call(boolean2), '[object Boolean]');
48 | assert(Object.prototype.toString.call(boolean3), '[object Boolean]');
49 | assert(Object.prototype.toString.call(boolean4), '[object Boolean]');
50 |
51 | assert((boolean1).toString(), 'true');
52 | assert((boolean2).toString(), 'false');
53 | assert((boolean3).toString(), 'true');
54 | assert((boolean4).toString(), 'false');
55 |
56 | });
57 |
58 | describe('Boolean.prototype.valueOf()', function(assert) {
59 |
60 | let boolean1 = true;
61 | let boolean2 = false;
62 | let boolean3 = new Boolean(true);
63 | let boolean4 = new Boolean(false);
64 |
65 | assert((boolean1).valueOf(), true);
66 | assert((boolean2).valueOf(), false);
67 | assert((boolean3).valueOf(), true);
68 | assert((boolean4).valueOf(), false);
69 |
70 | assert(JSON.stringify(boolean1), 'true');
71 | assert(JSON.stringify(boolean2), 'false');
72 | assert(JSON.stringify(boolean3), 'true');
73 | assert(JSON.stringify(boolean4), 'false');
74 |
75 | });
76 |
77 |
78 | export default finish('base/Boolean', {
79 | internet: false,
80 | network: false
81 | });
82 |
83 |
--------------------------------------------------------------------------------
/browser/source/ENVIRONMENT.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { URL } from '../source/parser/URL.mjs';
3 |
4 |
5 |
6 | const flags = ((global) => {
7 |
8 | let flags = {
9 | cause: null,
10 | code: null,
11 | debug: false,
12 | url: URL.parse()
13 | };
14 |
15 | let tmp1 = (global.location || {}).search || '';
16 | let tmp2 = (global.parent.location || {}).search || '';
17 | if (tmp1.includes('debug') === true) {
18 | flags.debug = true;
19 | } else if (tmp2.includes('debug') === true) {
20 | flags.debug = true;
21 | }
22 |
23 | if (tmp1.startsWith('?') === true) {
24 |
25 | tmp1.substr(1).split('&').forEach((flag) => {
26 |
27 | let tmp = flag.split('=');
28 | if (tmp.length >= 2) {
29 |
30 | let key = tmp[0];
31 | let val = tmp.slice(1).join('=');
32 |
33 | if (key === 'url') {
34 |
35 | flags[key] = URL.parse(decodeURIComponent(val));
36 |
37 | } else {
38 |
39 | let num = parseInt(val, 10);
40 | if (Number.isNaN(num) === false && (num).toString() === val) {
41 | val = num;
42 | }
43 |
44 | if (val === 'true') val = true;
45 | if (val === 'false') val = false;
46 | if (val === 'null') val = null;
47 |
48 | flags[key] = val;
49 |
50 | }
51 |
52 | }
53 |
54 | });
55 |
56 | }
57 |
58 | return flags;
59 |
60 | })(typeof window !== 'undefined' ? window : this);
61 |
62 | const hostname = ((global) => {
63 |
64 | let host = 'localhost';
65 |
66 | let tmp1 = (global.location || {}).host || '';
67 | if (tmp1.includes(':') === true) {
68 |
69 | let tmp2 = tmp1.split(':').slice(0, -1).join(':');
70 | if (tmp2 !== 'localhost') {
71 | host = tmp2;
72 | }
73 |
74 | } else if (tmp1 !== '') {
75 | host = tmp1;
76 | }
77 |
78 | return host;
79 |
80 | })(typeof window !== 'undefined' ? window : this);
81 |
82 | const secure = ((global) => {
83 |
84 | let secure = true;
85 |
86 | let tmp1 = (global.location || {}).protocol || '';
87 | if (tmp1.includes(':') === true) {
88 |
89 | let tmp2 = tmp1.split(':').shift();
90 | if (tmp2 !== 'https') {
91 | secure = false;
92 | }
93 |
94 | }
95 |
96 | return secure;
97 |
98 | })(typeof window !== 'undefined' ? window : this);
99 |
100 |
101 | const ENVIRONMENT = {
102 |
103 | flags: flags,
104 | hostname: hostname,
105 | secure: secure
106 |
107 | };
108 |
109 |
110 | export { ENVIRONMENT };
111 |
112 |
--------------------------------------------------------------------------------
/browser/internal/fix-host.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | stealth:fix-host
6 |
22 |
23 |
24 |
25 | Fix Host
26 |
27 | Stealth could not connect to the cached hosts for this domain.
28 |
29 |
30 | Please verify the correctness of the IPv4/IPv6 addresses.
31 | If they are incorrect, set them manually.
32 |
33 |
34 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/.github/workflows/covert-scan.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Covert Scan
3 |
4 | on: [push]
5 |
6 | jobs:
7 |
8 | scan-base:
9 |
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - run: |
15 | sudo apt-get install tcpdump
16 | sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/tcpdump
17 | - uses: actions/setup-node@v1
18 | with:
19 | node-version: 14
20 | - run: npm install
21 | - run: node ./make.mjs build
22 | - run: |
23 | cd ./base
24 | node ../covert/covert.mjs scan "base/*" --debug=true --report=../.github/workflows/covert-scan-base
25 | - uses: actions/upload-artifact@v2
26 | with:
27 | name: Report for Base
28 | path: .github/workflows/covert-scan-base.*
29 | - run: node .github/workflows/covert-scan.mjs .github/workflows/covert-scan-base.report
30 |
31 | scan-covert:
32 |
33 | runs-on: ubuntu-latest
34 |
35 | steps:
36 | - uses: actions/checkout@v2
37 | - run: |
38 | sudo apt-get install tcpdump
39 | sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/tcpdump
40 | - uses: actions/setup-node@v1
41 | with:
42 | node-version: 14
43 | - run: npm install
44 | - run: node ./make.mjs build
45 | - run: |
46 | cd ./covert
47 | node ../covert/covert.mjs scan "covert/*" --debug=true --report=../.github/workflows/covert-scan-covert
48 | - uses: actions/upload-artifact@v2
49 | with:
50 | name: Report for Covert
51 | path: .github/workflows/covert-scan-covert.*
52 | - run: node .github/workflows/covert-scan.mjs .github/workflows/covert-scan-covert.report
53 |
54 | scan-stealth:
55 |
56 | runs-on: ubuntu-latest
57 |
58 | steps:
59 | - uses: actions/checkout@v2
60 | - run: |
61 | sudo apt-get install tcpdump
62 | sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/tcpdump
63 | - uses: actions/setup-node@v1
64 | with:
65 | node-version: 14
66 | - run: npm install
67 | - run: node ./make.mjs build
68 | - run: |
69 | cd ./stealth
70 | node ../covert/covert.mjs scan "stealth/*" --debug=true --report=../.github/workflows/covert-scan-stealth
71 | - uses: actions/upload-artifact@v2
72 | with:
73 | name: Report for Stealth
74 | path: .github/workflows/covert-scan-stealth.*
75 | - run: node .github/workflows/covert-scan.mjs .github/workflows/covert-scan-stealth.report
76 |
77 |
--------------------------------------------------------------------------------
/browser/design/sheet/Console.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Assistant } from '../Assistant.mjs';
3 | import { Element } from '../Element.mjs';
4 | import { Widget } from '../Widget.mjs';
5 |
6 |
7 |
8 | const ASSISTANT = new Assistant({
9 | name: 'Console',
10 | widget: 'sheet/Console',
11 | events: {
12 | 'hide': 'Hiding Console.',
13 | 'show': 'Showing Console.'
14 | }
15 | });
16 |
17 | const Console = function(/* browser */) {
18 |
19 | this.button = new Element('button');
20 | this.console = Element.query('base-console');
21 | this.element = new Element('browser-sheet-console', [
22 | this.button
23 | ]);
24 |
25 | this.button.attr('title', 'Toggle visibility of Developer Console');
26 |
27 | this.button.on('click', () => {
28 |
29 | if (this.element.state() === 'active') {
30 | this.element.emit('hide');
31 | } else {
32 | this.element.emit('show');
33 | }
34 |
35 | });
36 |
37 | if (this.console !== null) {
38 |
39 | this.console.on('contextmenu', (e) => {
40 |
41 | let context = Widget.query('browser-menu-context');
42 | if (context !== null) {
43 |
44 | let element = Element.toElement(e.target);
45 | if (element.type === 'i') {
46 | element = Element.toElement(e.target.parentNode);
47 | }
48 |
49 | if (element.type === 'base-console-line') {
50 |
51 | let area = element.area();
52 | if (area !== null) {
53 |
54 | context.value([{
55 | label: 'copy',
56 | value: element.value()
57 | }]);
58 |
59 | context.area({
60 | x: e.x,
61 | y: e.y
62 | });
63 |
64 | context.emit('show');
65 |
66 | }
67 |
68 | }
69 |
70 | }
71 |
72 | });
73 |
74 | }
75 |
76 | this.element.on('show', () => {
77 |
78 | if (this.console === null) {
79 | this.console = Element.query('base-console');
80 | }
81 |
82 | if (this.console !== null) {
83 | this.console.state('active');
84 | }
85 |
86 | this.element.state('active');
87 | this.button.state('active');
88 | ASSISTANT.emit('show');
89 |
90 | });
91 |
92 | this.element.on('hide', () => {
93 |
94 | if (this.console === null) {
95 | this.console = Element.query('base-console');
96 | }
97 |
98 | if (this.console !== null) {
99 | this.console.state('');
100 | }
101 |
102 | if (this.element.state() === 'active') {
103 | this.element.state('');
104 | this.button.state('');
105 | ASSISTANT.emit('hide');
106 | }
107 |
108 | });
109 |
110 |
111 | Widget.call(this);
112 |
113 | };
114 |
115 |
116 | Console.prototype = Object.assign({}, Widget.prototype);
117 |
118 |
119 | export { Console };
120 |
121 |
--------------------------------------------------------------------------------
/base/review/Object.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import { Object, isObject } from '../source/Object.mjs';
4 |
5 |
6 |
7 | describe('Object.isObject()', function(assert) {
8 |
9 | let object1 = { foo: 'bar' };
10 | let object2 = new Object({ foo: 'bar' });
11 |
12 | assert(typeof Object.isObject, 'function');
13 |
14 | assert(Object.isObject(object1), true);
15 | assert(Object.isObject(object2), true);
16 |
17 | });
18 |
19 | describe('isObject()', function(assert) {
20 |
21 | let object1 = { foo: 'bar' };
22 | let object2 = new Object({ foo: 'bar' });
23 |
24 | assert(typeof isObject, 'function');
25 |
26 | assert(isObject(object1), true);
27 | assert(isObject(object2), true);
28 |
29 | });
30 |
31 | describe('Object.clone()', function(assert) {
32 |
33 | let object1 = { foo: 'bar' };
34 | let object2 = new Object({ foo: 'bar' });
35 | let object3 = { foo: 'bar', qux: [ 1, 3, 3, 7 ] };
36 | let object4 = new Object({ foo: 'bar', qux: [ 1, 3, 3, 7 ] });
37 |
38 | let target3 = {};
39 | let target4 = {};
40 | let clone1 = Object.clone(null, object1);
41 | let clone2 = Object.clone(null, object2);
42 | let clone3 = Object.clone(target3, object3);
43 | let clone4 = Object.clone(target4, object4);
44 |
45 | assert(isObject(clone1), true);
46 | assert(isObject(clone2), true);
47 | assert(isObject(clone3), true);
48 | assert(isObject(clone4), true);
49 |
50 | assert(object1 === clone1, false);
51 | assert(object2 === clone2, false);
52 | assert(object3 === clone3, false);
53 | assert(object4 === clone4, false);
54 |
55 | assert(target3 === clone3, true);
56 | assert(target4 === clone4, true);
57 |
58 | assert(object1, clone1);
59 | assert(object2, clone2);
60 | assert(object3, clone3);
61 | assert(object4, clone4);
62 |
63 | });
64 |
65 | describe('Object.prototype.toString()', function(assert) {
66 |
67 | let object1 = { foo: 'bar' };
68 | let object2 = new Object({ foo: 'bar' });
69 |
70 | assert(Object.prototype.toString.call(object1), '[object Object]');
71 | assert(Object.prototype.toString.call(object2), '[object Object]');
72 |
73 | assert((object1).toString(), '[object Object]');
74 | assert((object2).toString(), '[object Object]');
75 |
76 | });
77 |
78 | describe('Object.prototype.valueOf()', function(assert) {
79 |
80 | let object1 = { foo: 'bar' };
81 | let object2 = new Object({ foo: 'bar' });
82 |
83 | assert((object1).valueOf(), object1);
84 | assert((object2).valueOf(), object2);
85 |
86 | assert(JSON.stringify(object1), '{"foo":"bar"}');
87 | assert(JSON.stringify(object2), '{"foo":"bar"}');
88 |
89 | });
90 |
91 |
92 | export default finish('base/Object', {
93 | internet: false,
94 | network: false
95 | });
96 |
97 |
--------------------------------------------------------------------------------
/covert/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Covert Suite
3 |
4 | The Covert Suite is a test framework and test runner that allows to test
5 | and verify implementations of peer-to-peer network services, and the
6 | simulation of network behaviours (2G, 3G, 4G).
7 |
8 | The idea of its concept is that it can help Developers understand their
9 | code, and that Reviews can be executed multiple times in a stateless
10 | manner; even in parallel while fixing bugs in an implementation.
11 |
12 | All Reviews have been implemented and maintained in a manner so that they
13 | can be used as a reference point for future third-party clients.
14 |
15 | By default, it uses the [stealth/Client](../stealth/source/Client.mjs)
16 | which is written for node.js, so no external libraries and no external
17 | programs are necessary.
18 |
19 |
20 | ## Requirements
21 |
22 | Currently, Covert runs officially only on Arch Linux, though it might be
23 | possible to run it on MacOS Mojave and later, too.
24 |
25 | In order to simulate end-to-end throttled peer-to-peer Networking correctly,
26 | Covert requires these external packages on minimalistic Unix/Linux systems:
27 |
28 | ```bash
29 | # Install necessary packages
30 | pacman -S --needed iproute2 kmod net-tools sudo tcpdump
31 | ```
32 |
33 | Additionally, the following ports have to be allowed in the Firewall to
34 | transmit and receive of both UDP/TCP data:
35 |
36 | - `80` (`--internet=true`) for [stealth/connection/HTTP](../stealth/review/connection/HTTP.mjs) and [stealth/connection/WS](../stealth/review/connection/WS.mjs).
37 | - `443` (`--internet=true`) for [stealth/connection/HTTPS](../stealth/review/connection/HTTPS.mjs) and [stealth/connection/WSS](../stealth/review/connection/WSS.mjs).
38 | - `13337` (`--internet=true`) for [stealth/connection/HTTP](../stealth/review/connection/HTTP.mjs) and [stealth/connection/WS](../stealth/review/connection/WS.mjs).
39 | - `65432` for [stealth/Client](../stealth/review/Client.mjs) and [stealth/Server](../stealth/review/Server.mjs).
40 |
41 |
42 | ## Quickstart
43 |
44 | - Install [node.js](https://nodejs.org/en/download) latest (minimum version `12`).
45 |
46 | ```bash
47 | cd /path/to/stealth;
48 |
49 | # Build Stealth and Covert
50 | node ./covert/make.mjs;
51 |
52 | # Show Help
53 | node ./covert/covert.mjs;
54 | ```
55 |
56 |
57 | ## ECMAScript Usage
58 |
59 | The Covert Library exports a `default` export that contains a namespaced
60 | `Object`. Due to otherwise conflicting names, there are no separately
61 | named exports available.
62 |
63 | ```javascript
64 | // Import Covert Library
65 | import covert from './covert/index.mjs';
66 | ```
67 |
68 |
69 | ## NPM Usage
70 |
71 | ```javascript
72 | // Import Covert Library
73 | import covert from 'stealth/covert';
74 | ```
75 |
76 |
--------------------------------------------------------------------------------
/browser/design/appbar/Splitter.css:
--------------------------------------------------------------------------------
1 |
2 | browser-appbar-splitter {
3 | display: block;
4 | position: absolute;
5 | width: 100%;
6 | height: 34px;
7 | line-height: 32px;
8 | top: 0px;
9 | right: 32px;
10 | bottom: 0px;
11 | left: 0px;
12 | border: 0px solid transparent;
13 | border-bottom: 1px solid var(--surface-default-color);
14 | background: var(--surface-default-foreground);
15 | box-sizing: border-box;
16 | text-align: left;
17 | vertical-align: middle;
18 | pointer-events: none;
19 | transition: 200ms all ease-out;
20 | opacity: 0;
21 | }
22 |
23 | browser-appbar-splitter.active {
24 | pointer-events: all;
25 | transition: 200ms all ease-out;
26 | opacity: 1;
27 | }
28 |
29 | browser-appbar button[data-key="splitter"] {
30 | display: inline-block;
31 | position: relative;
32 | min-width: 32px;
33 | margin: 0px;
34 | padding: 0px;
35 | line-height: 32px;
36 | height: 32px;
37 | color: var(--element-default-color);
38 | font-size: 24px;
39 | text-align: center;
40 | background: var(--element-default-background);
41 | border: 1px solid transparent;
42 | border-radius: 4px;
43 | box-sizing: border-box;
44 | transition: 200ms all ease-out;
45 | vertical-align: top;
46 | user-select: none;
47 | -webkit-user-select: none;
48 | appearance: none;
49 | -moz-appearance: none;
50 | -webkit-appearance: none;
51 | outline: none;
52 | cursor: pointer;
53 | }
54 |
55 | browser-appbar button[data-key="splitter"].active {
56 | color: var(--element-active-color);
57 | }
58 |
59 | browser-appbar button[data-key="splitter"]:before {
60 | display: inline-block;
61 | width: 100%;
62 | font-family: 'icon';
63 | text-align: center;
64 | speak: none;
65 | -webkit-font-smoothing: antialiased;
66 | content: '\e5d4';
67 | }
68 |
69 | browser-appbar-splitter > browser-appbar-mode {
70 | width: calc(100% - 128px - 96px);
71 | }
72 |
73 |
74 |
75 | @media screen and (max-width: 640px) {
76 |
77 | browser-appbar-splitter {
78 | width: calc(100% - 32px);
79 | }
80 |
81 | }
82 |
83 | @media screen and (max-width: 412px) {
84 |
85 | browser-appbar-splitter {
86 | width: 100%;
87 | height: 64px;
88 | top: 0px;
89 | right: 0px;
90 | bottom: auto;
91 | left: 0px;
92 | transform: translate(0%, -100%);
93 | }
94 |
95 | browser-appbar-splitter.active {
96 | transform: translate(0%, 0%);
97 | }
98 |
99 | browser-appbar-splitter > browser-appbar-settings {
100 | width: calc(100% - 128px - 32px);
101 | text-align: right;
102 | }
103 |
104 | browser-appbar-splitter > browser-appbar-mode {
105 | display: block;
106 | position: absolute;
107 | width: 100%;
108 | top: 32px;
109 | right: 0px;
110 | bottom: auto;
111 | left: 0px;
112 | }
113 |
114 | }
115 |
116 |
--------------------------------------------------------------------------------
/base/review/Array.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { describe, finish } from '../../covert/index.mjs';
3 | import { Array, isArray } from '../source/Array.mjs';
4 |
5 |
6 |
7 | describe('Array.isArray()', function(assert) {
8 |
9 | let array1 = [ 1, 3, 3, 7 ];
10 | let array2 = new Array(1, 3, 3, 7);
11 |
12 | assert(typeof Array.isArray, 'function');
13 |
14 | assert(Array.isArray(array1), true);
15 | assert(Array.isArray(array2), true);
16 |
17 | });
18 |
19 | describe('isArray()', function(assert) {
20 |
21 | let array1 = [ 1, 3, 3, 7 ];
22 | let array2 = new Array(1, 3, 3, 7);
23 |
24 | assert(typeof isArray, 'function');
25 |
26 | assert(isArray(array1), true);
27 | assert(isArray(array2), true);
28 |
29 | });
30 |
31 | describe('Array.prototype.remove()', function(assert) {
32 |
33 | let array1 = [ 1, 3, 3, 7 ];
34 | let array2 = new Array(1, 3, 3, 7);
35 |
36 | assert(array1.remove(3), array1);
37 | assert(array2.remove(3), array1);
38 |
39 | assert(array1, [ 1, 7 ]);
40 | assert(array2, new Array(1, 7));
41 |
42 | });
43 |
44 | describe('Array.prototype.removeEvery()', function(assert) {
45 |
46 | let array1 = [
47 | { num: 1, str: 'one' },
48 | { num: 3, str: 'three' },
49 | { num: 3, str: 'three' },
50 | { num: 7, str: 'seven' }
51 | ];
52 |
53 | let array2 = [
54 | { num: 1, str: 'one' },
55 | { num: 3, str: 'three' },
56 | { num: 3, str: 'three' },
57 | { num: 7, str: 'seven' }
58 | ];
59 |
60 | let return1 = array1.removeEvery((cell) => cell.num === 3);
61 | let return2 = array2.removeEvery((cell) => cell.str === 'three');
62 |
63 | assert(return1, array1);
64 | assert(return2, array2);
65 |
66 | assert(array1, [
67 | { num: 1, str: 'one' },
68 | { num: 7, str: 'seven' }
69 | ]);
70 |
71 | assert(array2, [
72 | { num: 1, str: 'one' },
73 | { num: 7, str: 'seven' }
74 | ]);
75 |
76 | });
77 |
78 | describe('Array.prototype.toString()', function(assert) {
79 |
80 | let array1 = [ 1, 3, 3, 7 ];
81 | let array2 = new Array(1, 3, 3, 7);
82 |
83 | assert(Object.prototype.toString.call(array1), '[object Array]');
84 | assert(Object.prototype.toString.call(array2), '[object Array]');
85 |
86 | assert(array1.toString(), '1,3,3,7');
87 | assert(array2.toString(), '1,3,3,7');
88 |
89 | });
90 |
91 | describe('Array.prototype.valueOf()', function(assert) {
92 |
93 | let array1 = [ 1, 3, 3, 7 ];
94 | let array2 = new Array(1, 3, 3, 7);
95 |
96 | assert(array1.valueOf(), array1);
97 | assert(array2.valueOf(), array2);
98 |
99 | assert(JSON.stringify(array1), '[1,3,3,7]');
100 | assert(JSON.stringify(array2), '[1,3,3,7]');
101 |
102 | });
103 |
104 |
105 | export default finish('base/Array', {
106 | internet: false,
107 | network: false
108 | });
109 |
110 |
--------------------------------------------------------------------------------
/browser/design/widget/Audio.css:
--------------------------------------------------------------------------------
1 |
2 | browser-widget-audio {
3 | all: unset;
4 | display: block;
5 | position: relative;
6 | width: 800px;
7 | margin: 0px;
8 | padding: 16px;
9 | background: var(--surface-default-background);
10 | border: 1px solid var(--surface-default-color);
11 | border-radius: 8px;
12 | box-sizing: border-box;
13 | transition: 200ms all ease-out;
14 | overflow: hidden;
15 | cursor: default;
16 | }
17 |
18 |
19 |
20 | browser-widget-audio-article {
21 | display: block;
22 | position: relative;
23 | width: 100%;
24 | height: auto;
25 | text-align: center;
26 | overflow: hidden;
27 | z-index: 1;
28 | }
29 |
30 |
31 |
32 | browser-widget-audio-footer {
33 | display: block;
34 | height: 32px;
35 | margin: 0px;
36 | padding: 0px;
37 | text-align: left;
38 | z-index: 2;
39 | }
40 |
41 | browser-widget-audio-footer > button[data-action="play"],
42 | browser-widget-audio-footer > button[data-action="pause"] {
43 | margin: 0px 16px 0px 0px;
44 | }
45 |
46 | browser-widget-audio-footer > button[data-action="download"] {
47 | margin: 0px 0px 0px 16px;
48 | }
49 |
50 | browser-widget-audio-footer > button[data-action="download"]:before { content: '\e2c4'; }
51 | browser-widget-audio-footer > button[data-action="play"]:before { content: '\e037'; }
52 | browser-widget-audio-footer > button[data-action="pause"]:before { content: '\e034'; }
53 |
54 | browser-widget-audio-footer > progress {
55 | display: inline-block;
56 | width: calc(100% - 48px - 48px);
57 | height: 16px;
58 | line-height: 30px;
59 | border: 1px solid transparent;
60 | border-radius: 4px;
61 | box-sizing: border-box;
62 | appearance: none;
63 | -moz-appearance: none;
64 | -webkit-appearance: none;
65 | vertical-align: middle;
66 | cursor: col-resize;
67 | }
68 |
69 | browser-widget-audio-footer > progress::-moz-progress-bar {
70 | display: inline-block;
71 | height: 100%;
72 | background: var(--element-focus-color);
73 | border-radius: 4px 0px 0px 4px;
74 | vertical-align: middle;
75 | box-sizing: border-box;
76 | }
77 |
78 | browser-widget-audio-footer > progress[value="100"]::-moz-progress-bar {
79 | border-radius: 4px;
80 | }
81 |
82 | browser-widget-audio-footer > progress::-webkit-progress-bar {
83 | background: var(--element-default-color);
84 | border-radius: 4px;
85 | }
86 |
87 | browser-widget-audio-footer > progress::-webkit-progress-value {
88 | background: var(--element-focus-color);
89 | border-radius: 4px 0px 0px 4px;
90 | }
91 |
92 | browser-widget-audio-footer > progress[value="100"]::-webkit-progress-value {
93 | border-radius: 4px;
94 | }
95 |
96 |
97 |
98 | @media screen and (max-width: 800px) {
99 |
100 | browser-widget-audio {
101 | width: auto;
102 | }
103 |
104 | }
105 |
106 |
--------------------------------------------------------------------------------
/browser/design/appbar/Splitter.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Element } from '../Element.mjs';
3 | import { Widget } from '../Widget.mjs';
4 |
5 |
6 |
7 | const update = function() {
8 |
9 | let appbar = Element.query('browser-appbar');
10 | let element = this.element;
11 |
12 | if (appbar !== null && element !== null) {
13 |
14 | this.appbar['address'].erase();
15 | this.appbar['history'].erase();
16 | this.appbar['mode'].erase();
17 | this.appbar['settings'].erase();
18 | this.button.erase();
19 |
20 | if (this.__state.mobile === true) {
21 |
22 | this.appbar['history'].render(element);
23 |
24 | this.appbar['address'].render(appbar);
25 | this.button.render(appbar);
26 |
27 | this.appbar['mode'].render(element);
28 | this.appbar['settings'].render(element);
29 |
30 | element.render(appbar);
31 |
32 | } else {
33 |
34 | this.appbar['history'].render(appbar);
35 | this.appbar['address'].render(appbar);
36 | this.appbar['mode'].render(appbar);
37 | this.appbar['settings'].render(appbar);
38 |
39 | element.erase();
40 |
41 | }
42 |
43 | }
44 |
45 | };
46 |
47 |
48 |
49 | const Splitter = function(/* browser */) {
50 |
51 | this.element = new Element('browser-appbar-splitter');
52 |
53 | this.__state = {
54 | mobile: false
55 | };
56 |
57 |
58 | this.appbar = {
59 | 'history': Widget.query('browser-appbar-history'),
60 | 'address': Widget.query('browser-appbar-address'),
61 | 'mode': Widget.query('browser-appbar-mode'),
62 | 'settings': Widget.query('browser-appbar-settings')
63 | };
64 |
65 | this.button = new Element('button');
66 | this.button.attr('data-key', 'splitter');
67 | this.button.attr('title', 'Show additional Settings');
68 |
69 | this.button.on('click', () => {
70 |
71 | if (this.element.state() === 'active') {
72 | this.element.emit('hide');
73 | } else {
74 | this.element.emit('show');
75 | }
76 |
77 | });
78 |
79 | this.element.on('hide', () => {
80 |
81 | this.button.state('');
82 | this.element.state('');
83 |
84 | });
85 |
86 | this.element.on('resize', (width /*, height */) => {
87 |
88 | let old_state = this.__state.mobile;
89 | let new_state = old_state;
90 |
91 | if (width < 640) {
92 | new_state = true;
93 | } else {
94 | new_state = false;
95 | }
96 |
97 | if (old_state !== new_state) {
98 | this.__state.mobile = new_state;
99 | update.call(this);
100 | }
101 |
102 | });
103 |
104 | this.element.on('show', () => {
105 |
106 | this.button.state('active');
107 | this.element.state('active');
108 |
109 | });
110 |
111 |
112 | Widget.call(this);
113 |
114 | };
115 |
116 |
117 | Splitter.prototype = Object.assign({}, Widget.prototype);
118 |
119 |
120 | export { Splitter };
121 |
122 |
--------------------------------------------------------------------------------
/browser/design/common/layout.css:
--------------------------------------------------------------------------------
1 |
2 | ::selection {
3 | color: var(--element-select-color);
4 | border-radius: 4px;
5 | background: var(--element-select-background);
6 | cursor: text;
7 | }
8 |
9 | ::-moz-selection {
10 | color: var(--element-select-color);
11 | border-radius: 4px;
12 | background: var(--element-select-background);
13 | cursor: text;
14 | }
15 |
16 | ::-webkit-scrollbar {
17 | width: 16px;
18 | border: 0px solid transparent;
19 | background: var(--scrollbar-default-background);
20 | }
21 |
22 | ::-webkit-scrollbar-thumb {
23 | width: 16px;
24 | border: 0px solid transparent;
25 | border-radius: 2px;
26 | background: var(--scrollbar-default-color);
27 | }
28 |
29 |
30 |
31 | body {
32 | margin: 0px;
33 | padding: 0px;
34 | }
35 |
36 | body > article {
37 | display: block;
38 | width: 800px;
39 | margin: 64px auto 0px auto;
40 | padding: 16px;
41 | font-family: 'museo';
42 | font-size: 16px;
43 | background: var(--surface-default-background);
44 | border: 1px solid var(--surface-default-color);
45 | border-radius: 8px;
46 | box-sizing: border-box;
47 | }
48 |
49 | body > article h3 {
50 | margin: 0px 0px 16px 0px;
51 | padding: 0px;
52 | font-family: 'crystalline';
53 | font-size: 32px;
54 | font-weight: 500;
55 | line-height: 32px;
56 | text-transform: uppercase;
57 | border-bottom: 1px solid var(--surface-default-color);
58 | }
59 |
60 | body > article p {
61 | margin: 0px 0px 16px 0px;
62 | font-family: 'museo';
63 | font-size: 16px;
64 | text-align: justify;
65 | }
66 |
67 | body > article code {
68 | display: block;
69 | margin: 0px;
70 | padding: 4px;
71 | font-family: 'vera-mono';
72 | color: var(--layout-default-color);
73 | background: var(--layout-default-background);
74 | border-radius: 4px;
75 | }
76 |
77 | body > article table {
78 | width: 100%;
79 | text-align: center;
80 | }
81 |
82 | body > article p + table {
83 | margin-top: 32px;
84 | }
85 |
86 | body > article table th {
87 | margin: 0px;
88 | padding: 0px;
89 | font-family: 'crystalline';
90 | font-size: 24px;
91 | font-weight: 500;
92 | line-height: 32px;
93 | text-align: center;
94 | text-transform: uppercase;
95 | }
96 |
97 | body > article table td {
98 | margin: 0px;
99 | padding: 8px 8px;
100 | font-size: 16px;
101 | line-height: 16px;
102 | font-family: 'museo';
103 | border-top: 1px solid var(--surface-default-color);
104 | }
105 |
106 | body > footer {
107 | display: block;
108 | width: 100%;
109 | margin: 64px 0px 0px 0px;
110 | padding: 16px;
111 | font-family: 'museo';
112 | text-align: center;
113 | font-size: 16px;
114 | box-sizing: border-box;
115 | }
116 |
117 |
118 |
119 | @media screen and (max-width: 800px) {
120 |
121 | body > article {
122 | width: auto;
123 | }
124 |
125 | }
126 |
127 |
--------------------------------------------------------------------------------
/stealth/review/connection/WHOIS.mjs:
--------------------------------------------------------------------------------
1 |
2 | import net from 'net';
3 |
4 | import { Buffer, isArray, isBuffer, isFunction, isObject } from '../../../base/index.mjs';
5 | import { describe, finish } from '../../../covert/index.mjs';
6 | import { WHOIS } from '../../../stealth/source/connection/WHOIS.mjs';
7 | import { IP } from '../../../stealth/source/parser/IP.mjs';
8 | import { URL } from '../../../stealth/source/parser/URL.mjs';
9 |
10 |
11 |
12 | describe('WHOIS.connect()', function(assert) {
13 |
14 | assert(isFunction(WHOIS.connect), true);
15 |
16 | let url = Object.assign(URL.parse('whois://whois.ripe.net'), { hosts: [ IP.parse('193.0.6.135'), IP.parse('2001:067c:02e8:0022:0000:0000:c100:0687') ] });
17 | let connection = WHOIS.connect(url);
18 |
19 | connection.once('@connect', () => {
20 |
21 | assert(true);
22 |
23 | setTimeout(() => {
24 | connection.disconnect();
25 | }, 0);
26 |
27 | });
28 |
29 | connection.once('@disconnect', () => {
30 | assert(true);
31 | });
32 |
33 | });
34 |
35 | describe('WHOIS.disconnect()', function(assert) {
36 |
37 | assert(isFunction(WHOIS.disconnect), true);
38 |
39 | let url = Object.assign(URL.parse('whois://whois.ripe.net'), { hosts: [ IP.parse('193.0.6.135'), IP.parse('2001:067c:02e8:0022:0000:0000:c100:0687') ] });
40 | let connection = WHOIS.connect(url);
41 |
42 | connection.once('@connect', () => {
43 |
44 | assert(true);
45 |
46 | setTimeout(() => {
47 | assert(WHOIS.disconnect(connection), true);
48 | }, 0);
49 |
50 | });
51 |
52 | connection.once('@disconnect', () => {
53 | assert(true);
54 | });
55 |
56 | });
57 |
58 | // TODO: WHOIS.receive() for AFRINIC format
59 | // TODO: WHOIS.receive() for APNIC format
60 | // TODO: WHOIS.receive() for ARIN format
61 | // TODO: WHOIS.receive() for LACNIC format
62 | // TODO: WHOIS.receive() for RIPE format
63 |
64 | describe('WHOIS.send()/RIPE', function(assert, console) {
65 |
66 | assert(isFunction(WHOIS.send), true);
67 |
68 | let url = Object.assign(URL.parse('whois://whois.ripe.net'), { hosts: [ IP.parse('193.0.6.135'), IP.parse('2001:067c:02e8:0022:0000:0000:c100:0687') ] });
69 | let connection = WHOIS.connect(url);
70 |
71 | connection.once('response', (response) => {
72 | console.log(response);
73 | });
74 |
75 | connection.once('@connect', () => {
76 |
77 | assert(true);
78 |
79 | setTimeout(() => {
80 | assert(WHOIS.disconnect(connection), true);
81 | }, 0);
82 |
83 | });
84 |
85 | connection.once('@disconnect', () => {
86 | assert(true);
87 | });
88 |
89 | });
90 |
91 |
92 | export default finish('stealth/connection/WHOIS', {
93 | internet: true,
94 | network: true,
95 | ports: [ 43, 13337 ]
96 | });
97 |
98 |
--------------------------------------------------------------------------------
/browser/internal/fix-connection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | stealth:fix-connection
6 |
14 |
15 |
16 |
17 | Fix Connection
18 |
19 | Stealth could not request the entered URL successfully,
20 | because either the internet connection broke down or
21 | the server is a teapot .
22 |
23 |
24 | The server responded with incorrect HTTP headers with status that don't make sense.
25 |
26 |
27 | The server responded with a payload that seems to be incomplete or
28 | injected by a third-party and therefore potentially malicious.
29 |
30 |
31 | The configured Proxy does not support anonymous SOCKS5 tunnels.
32 |
33 |
34 | The socket connection is too unstable to resume the download.
35 | Please check the stability of your internet connection.
36 |
37 |
38 | The socket encryption could not be verified. Please double-check your
39 | backyard and windows, the Feds or some Blackhat might be listening.
40 |
41 |
42 |
43 | Download via Peers
44 |
45 | TODO: Peer Download
46 |
47 |
48 |
49 | Download via Web Archive
50 |
51 | TODO: Web Archive Download
52 |
53 |
54 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/stealth/source/client/service/Host.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Emitter, isFunction, isObject } from '../../../extern/base.mjs';
3 |
4 |
5 |
6 | const Host = function(client) {
7 |
8 | this.client = client;
9 | Emitter.call(this);
10 |
11 | };
12 |
13 |
14 | Host.prototype = Object.assign({}, Emitter.prototype, {
15 |
16 | toJSON: function() {
17 |
18 | let blob = Emitter.prototype.toJSON.call(this);
19 | let data = {
20 | events: blob.data.events,
21 | journal: blob.data.journal
22 | };
23 |
24 | return {
25 | 'type': 'Host Service',
26 | 'data': data
27 | };
28 |
29 | },
30 |
31 | read: function(payload, callback) {
32 |
33 | payload = isObject(payload) ? payload : null;
34 | callback = isFunction(callback) ? callback : null;
35 |
36 |
37 | if (payload !== null && callback !== null) {
38 |
39 | this.once('read', (response) => callback(response));
40 |
41 | this.client.send({
42 | headers: {
43 | service: 'host',
44 | method: 'read'
45 | },
46 | payload: payload
47 | });
48 |
49 | } else if (callback !== null) {
50 | callback(null);
51 | }
52 |
53 | },
54 |
55 | resolve: function(payload, callback) {
56 |
57 | payload = isObject(payload) ? payload : null;
58 | callback = isFunction(callback) ? callback : null;
59 |
60 |
61 | if (payload !== null && callback !== null) {
62 |
63 | this.once('resolve', (response) => callback(response));
64 |
65 | this.client.send({
66 | headers: {
67 | service: 'host',
68 | method: 'resolve'
69 | },
70 | payload: payload
71 | });
72 |
73 | } else if (callback !== null) {
74 | callback(null);
75 | }
76 |
77 | },
78 |
79 | remove: function(payload, callback) {
80 |
81 | payload = isObject(payload) ? payload : null;
82 | callback = isFunction(callback) ? callback : null;
83 |
84 |
85 | if (payload !== null && callback !== null) {
86 |
87 | this.once('remove', (result) => callback(result));
88 |
89 | this.client.send({
90 | headers: {
91 | service: 'host',
92 | method: 'remove'
93 | },
94 | payload: payload
95 | });
96 |
97 | } else if (callback !== null) {
98 | callback(false);
99 | }
100 |
101 | },
102 |
103 | save: function(payload, callback) {
104 |
105 | payload = isObject(payload) ? payload : null;
106 | callback = isFunction(callback) ? callback : null;
107 |
108 |
109 | if (payload !== null && callback !== null) {
110 |
111 | this.once('save', (result) => callback(result));
112 |
113 | this.client.send({
114 | headers: {
115 | service: 'host',
116 | method: 'save'
117 | },
118 | payload: payload
119 | });
120 |
121 | } else if (callback !== null) {
122 | callback(false);
123 | }
124 |
125 | }
126 |
127 | });
128 |
129 |
130 | export { Host };
131 |
132 |
--------------------------------------------------------------------------------
/guide/concept/Service.md:
--------------------------------------------------------------------------------
1 |
2 | # Stealth Service API
3 |
4 | Stealth's Service API can be accessed via `WS/13` Sockets.
5 |
6 |
7 | ## Request Data and Response Data
8 |
9 | Both the requests and responses have the same `headers` format:
10 |
11 | - `service (String)` is the identifier of the equivalent Service.
12 | - `method (optional String)` is the name of the method on the Service's instance.
13 | - `event (optional String)` is the name of the event that is `emit()`ed on the Service's instance.
14 |
15 | The request `payload` varies from Service to Service, but the API
16 | always aims to be compatible with the `URL.parse()` syntax, and reuses
17 | the otherwise empty `headers` and `payload` property on the reference.
18 |
19 | For detailed usage, take a look at the [stealth/client](/stealth/review/client)
20 | Reviews that explain all typical API usage scenarios.
21 |
22 | The client-side and server-side Reviews can be run via Covert like this:
23 |
24 | ```bash
25 | cd ./path/to/stealth;
26 |
27 | cd ./stealth;
28 |
29 | node ../covert/covert.mjs scan stealth/server/service/*;
30 | node ../covert/covert.mjs scan stealth/client/service/*;
31 | ```
32 |
33 |
34 | ## WebSocket API (WS/13)
35 |
36 | These services are available on port `65432` via the `WS/13` Protocol. The
37 | `WebSocket Protocol` has to be set to `stealth` in order to make a successful
38 | handshake, other `Upgrade` requests are disconnected immediately.
39 |
40 | **IMPORTANT**: Stealth's [WS](/stealth/source/connection/WS.mjs) implementation
41 | does not support Binary Frames (`0x02`) on purpose to reduce the attack surface
42 | of fragmented TCP frames.
43 |
44 | The `Buffer` API that is used to transfer `payload`s will be serialized as an `Array`
45 | with Integers (`0-255`), and therefore Text Frames (`0x01`) with encoded `JSON`
46 | as `Payload` can be used.
47 |
48 | Note that the `Payload` on `WS/13` Framing level consists of an encoded `JSON.stringify()`
49 | string of an `Object` that contains the `Service Headers` and `Service Payload` which are
50 | delegated respectively on either side. To avoid confusion, the term `Payload` is not used
51 | and the term `Request` and `Response` are used henceforth.
52 |
53 |
54 | ```javascript
55 | // Example inside a Browser VM
56 |
57 | let socket = new WebSocket('ws://stealth-service:65432', [ 'stealth' ]);
58 |
59 | socket.onmessage = (e) => {
60 |
61 | let response = null;
62 |
63 | if (typeof e.data === 'string') {
64 | response = JSON.parse(e.data);
65 | }
66 |
67 | if (response !== null) {
68 | console.log('received headers', response.headers);
69 | console.log('received payload', response.payload);
70 | }
71 |
72 | };
73 |
74 | socket.onopen = () => {
75 |
76 | let request = {
77 | headers: {
78 | service: 'host',
79 | method: 'read'
80 | },
81 | payload: {
82 | domain: 'cookie.engineer'
83 | }
84 | };
85 |
86 | socket.send(JSON.stringify(request));
87 |
88 | };
89 | ```
90 |
91 |
--------------------------------------------------------------------------------
/stealth/source/server/service/Blocker.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Emitter, isFunction, isObject, isString } from '../../../extern/base.mjs';
3 |
4 |
5 |
6 | const toDomain = function(payload) {
7 |
8 | let domain = null;
9 |
10 | if (isObject(payload) === true) {
11 |
12 | if (isString(payload.domain) === true) {
13 |
14 | if (isString(payload.subdomain) === true) {
15 | domain = payload.subdomain + '.' + payload.domain;
16 | } else {
17 | domain = payload.domain;
18 | }
19 |
20 | } else if (isString(payload.host) === true) {
21 | domain = payload.host;
22 | }
23 |
24 | }
25 |
26 | return domain;
27 |
28 | };
29 |
30 |
31 |
32 | const Blocker = function(stealth) {
33 |
34 | this.stealth = stealth;
35 | Emitter.call(this);
36 |
37 | };
38 |
39 |
40 | /*
41 | * {
42 | * "domain": "ads.example.com"
43 | * }
44 | */
45 |
46 | Blocker.isBlocker = function(payload) {
47 |
48 | if (
49 | isObject(payload) === true
50 | && isString(payload.domain) === true
51 | ) {
52 | return true;
53 | }
54 |
55 |
56 | return false;
57 |
58 | };
59 |
60 | Blocker.toBlocker = function(payload) {
61 |
62 | if (isObject(payload) === true) {
63 |
64 | let domain = null;
65 |
66 | if (isString(payload.domain) === true) {
67 |
68 | if (isString(payload.subdomain) === true) {
69 | domain = payload.subdomain + '.' + payload.domain;
70 | } else {
71 | domain = payload.domain;
72 | }
73 |
74 | } else if (isString(payload.host) === true) {
75 | domain = payload.host;
76 | }
77 |
78 | if (domain !== null) {
79 |
80 | return {
81 | domain: domain
82 | };
83 |
84 | }
85 |
86 | }
87 |
88 |
89 | return null;
90 |
91 | };
92 |
93 |
94 | Blocker.prototype = Object.assign({}, Emitter.prototype, {
95 |
96 | toJSON: function() {
97 |
98 | let blob = Emitter.prototype.toJSON.call(this);
99 | let data = {
100 | events: blob.data.events,
101 | journal: blob.data.journal
102 | };
103 |
104 | return {
105 | 'type': 'Blocker Service',
106 | 'data': data
107 | };
108 |
109 | },
110 |
111 | read: function(payload, callback) {
112 |
113 | callback = isFunction(callback) ? callback : null;
114 |
115 |
116 | let blocker = null;
117 | let domain = toDomain(payload);
118 | if (domain !== null) {
119 | blocker = this.stealth.settings.blockers.find((b) => b.domain === domain) || null;
120 | }
121 |
122 | // Ensure that subdomains are matched, too
123 | if (blocker === null) {
124 | blocker = this.stealth.settings.blockers.find((b) => domain.endsWith('.' + b.domain)) || null;
125 | }
126 |
127 | if (callback !== null) {
128 |
129 | callback({
130 | headers: {
131 | service: 'blocker',
132 | event: 'read'
133 | },
134 | payload: blocker
135 | });
136 |
137 | }
138 |
139 | }
140 |
141 | });
142 |
143 |
144 | export { Blocker };
145 |
146 |
--------------------------------------------------------------------------------
/browser/design/card/Host.css:
--------------------------------------------------------------------------------
1 |
2 | browser-card-host {
3 | all: unset;
4 | display: block;
5 | position: relative;
6 | width: 800px;
7 | max-height: 64px;
8 | margin: 0px;
9 | padding: 16px;
10 | background: var(--surface-default-background);
11 | border: 1px solid var(--surface-default-color);
12 | border-radius: 8px;
13 | box-sizing: border-box;
14 | transition: 200ms all ease-out;
15 | overflow: hidden;
16 | cursor: default;
17 | }
18 |
19 | browser-card-host.active {
20 | max-height: unset;
21 | }
22 |
23 | browser-card-host > h3 {
24 | display: block;
25 | margin: 0px 0px 16px 0px;
26 | padding: 0px;
27 | }
28 |
29 | browser-card-host > h3:before {
30 | display: inline;
31 | content: '\e80d';
32 | margin: 0px 16px 0px 0px;
33 | padding: 0px;
34 | font-family: 'icon';
35 | font-size: 24px;
36 | font-weight: 400;
37 | text-align: center;
38 | vertical-align: middle;
39 | speak: none;
40 | -webkit-font-smoothing: antialiased;
41 | }
42 |
43 | browser-card-host > h3 input[type="text"] {
44 | display: inline-block;
45 | width: calc(100% - 32px - 32px - 24px);
46 | font-family: 'vera-mono';
47 | font-size: 16px;
48 | font-weight: 400;
49 | line-height: 32px;
50 | }
51 |
52 | browser-card-host > button[data-action="toggle"] {
53 | display: block;
54 | position: absolute;
55 | top: 16px;
56 | right: 16px;
57 | bottom: auto;
58 | left: auto;
59 | }
60 |
61 | browser-card-host > button[data-action="toggle"]:before {
62 | content: '\e5c5';
63 | transition: 200ms transform ease-out;
64 | }
65 |
66 | browser-card-host > button[data-action="toggle"].active:before {
67 | transform: rotate(180deg);
68 | transition: 200ms transform ease-out;
69 | }
70 |
71 |
72 |
73 | browser-card-host-article {
74 | display: none;
75 | width: 100%;
76 | height: auto;
77 | }
78 |
79 | browser-card-host.active browser-card-host-article {
80 | display: block;
81 | }
82 |
83 | browser-card-host-article > textarea {
84 | display: block;
85 | width: 100%;
86 | height: auto;
87 | background: transparent;
88 | resize: vertical;
89 | }
90 |
91 |
92 |
93 | browser-card-host-footer {
94 | display: none;
95 | height: 32px;
96 | margin: 16px 0px 0px 0px;
97 | padding: 0px;
98 | text-align: right;
99 | }
100 |
101 | browser-card-host.active browser-card-host-footer {
102 | display: block;
103 | }
104 |
105 | browser-card-host-footer > button {
106 | margin-left: 16px;
107 | }
108 |
109 | browser-card-host-footer > button[data-action="create"]:before { content: '\e145'; }
110 | browser-card-host-footer > button[data-action="refresh"]:before { content: '\e5d5'; }
111 | browser-card-host-footer > button[data-action="remove"]:before { content: '\e14c'; }
112 | browser-card-host-footer > button[data-action="save"]:before { content: '\e876'; }
113 |
114 |
115 |
116 | @media screen and (max-width: 800px) {
117 |
118 | browser-card-host {
119 | width: auto;
120 | }
121 |
122 | }
123 |
124 |
--------------------------------------------------------------------------------
/browser/design/common/theme.css:
--------------------------------------------------------------------------------
1 |
2 | body,
3 | body[data-theme="dark"] {
4 |
5 | color: #ffffff;
6 | background: #000000;
7 |
8 | --layout-default-color: #ffffff;
9 | --layout-default-background: #000000;
10 |
11 | --layout-focus-color: #ffffff;
12 | --layout-focus-background: rgba(255,255,255,0.20);
13 |
14 | --link-default-color: #22ccff;
15 | --link-default-background: transparent;
16 |
17 | --link-focus-color: #22ccff;
18 | --link-focus-background: rgba(255,255,255,0.20);
19 |
20 | --element-default-color: #aaaaaa;
21 | --element-default-background: transparent;
22 |
23 | --element-disable-color: #222222;
24 | --element-disable-background: transparent;
25 |
26 | --element-invalid-color: #ff2222;
27 | --element-invalid-background: rgba(255, 34, 34, 0.20);
28 |
29 | --element-focus-color: #ffffff;
30 | --element-focus-background: rgba(255,255,255,0.20);
31 |
32 | --element-active-color: #ffffff;
33 | --element-active-background: rgba(255,255,255,0.10);
34 |
35 | --element-select-color: #22ccff;
36 | --element-select-background: rgba(51,153,255,0.20);
37 |
38 | --scrollbar-default-color: #ffffff;
39 | --scrollbar-default-background: transparent;
40 |
41 | --surface-default-color: #666666;
42 | --surface-default-background: rgba(255, 255, 255, 0.10);
43 | --surface-default-foreground: #1a1a1a;
44 |
45 | /* Necessary image due to Browser Quirks */
46 | --element-select-default-background-image: url('./theme-dark-element-select-default.svg');
47 | --element-select-focus-background-image: url('./theme-dark-element-select-focus.svg');
48 |
49 | }
50 |
51 | body[data-theme="light"] {
52 |
53 | color: #111111;
54 | background: #eeeeee;
55 |
56 | --layout-default-color: #111111;
57 | --layout-default-background: #eeeeee;
58 |
59 | --layout-focus-color: #111111;
60 | --layout-focus-background: rgba(0,0,0,0.20);
61 |
62 | --link-default-color: #22ccff;
63 | --link-default-background: transparent;
64 |
65 | --link-focus-color: #22ccff;
66 | --link-focus-background: rgba(34,204,255,0.20);
67 |
68 | --element-default-color: #666666;
69 | --element-default-background: transparent;
70 |
71 | --element-disable-color: #dddddd;
72 | --element-disable-background: transparent;
73 |
74 | --element-invalid-color: #ff2222;
75 | --element-invalid-background: rgba(255, 34, 34, 0.20);
76 |
77 | --element-focus-color: #22ccff;
78 | --element-focus-background: rgba(34,204,255,0.20);
79 |
80 | --element-active-color: #22ccff;
81 | --element-active-background: rgba(34,204,255,0.10);
82 |
83 | --element-select-color: #22ccff;
84 | --element-select-background: rgba(34,204,255,0.20);
85 |
86 | --scrollbar-default-color: #111111;
87 | --scrollbar-default-background: transparent;
88 |
89 | --surface-default-color: #666666;
90 | --surface-default-background: rgba(0, 0, 0, 0.025);
91 | --surface-default-foreground: #e8e8e8;
92 |
93 | /* Necessary image due to Browser Quirks */
94 | --element-select-default-background-image: url('./theme-light-element-select-default.svg');
95 | --element-select-focus-background-image: url('./theme-light-element-select-focus.svg');
96 |
97 | }
98 |
99 |
--------------------------------------------------------------------------------
/stealth/review/client/service/Mode.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { isFunction } from '../../../../base/index.mjs';
3 | import { after, before, describe, finish } from '../../../../covert/index.mjs';
4 | import { Mode } from '../../../../stealth/source/client/service/Mode.mjs';
5 | import { connect as connect_stealth, disconnect as disconnect_stealth } from '../../../../stealth/review/Stealth.mjs';
6 | import { connect as connect_client, disconnect as disconnect_client } from '../../../../stealth/review/Client.mjs';
7 |
8 |
9 |
10 | before(connect_stealth);
11 | before(connect_client);
12 |
13 | describe('new Mode()', function(assert) {
14 |
15 | assert(this.client !== null);
16 | assert(this.client.services.mode instanceof Mode, true);
17 |
18 | });
19 |
20 | describe('Mode.prototype.toJSON()', function(assert) {
21 |
22 | assert(this.client !== null);
23 | assert(isFunction(this.client.services.mode.toJSON), true);
24 |
25 | assert(this.client.services.mode.toJSON(), {
26 | type: 'Mode Service',
27 | data: {
28 | events: [],
29 | journal: []
30 | }
31 | });
32 |
33 | });
34 |
35 | describe('Mode.prototype.save()', function(assert) {
36 |
37 | assert(this.client !== null);
38 | assert(isFunction(this.client.services.mode.save), true);
39 |
40 | this.client.services.mode.save({
41 | domain: 'example.com',
42 | mode: {
43 | text: false,
44 | image: true,
45 | audio: false,
46 | video: true,
47 | other: false
48 | }
49 | }, (response) => {
50 |
51 | assert(response, true);
52 |
53 | });
54 |
55 | });
56 |
57 | describe('Mode.prototype.read()/success', function(assert) {
58 |
59 | assert(this.client !== null);
60 | assert(isFunction(this.client.services.mode.read), true);
61 |
62 | this.client.services.mode.read({
63 | domain: 'example.com'
64 | }, (response) => {
65 |
66 | assert(response, {
67 | domain: 'example.com',
68 | mode: {
69 | text: false,
70 | image: true,
71 | audio: false,
72 | video: true,
73 | other: false
74 | }
75 | });
76 |
77 | });
78 |
79 | });
80 |
81 | describe('Mode.prototype.remove()', function(assert) {
82 |
83 | assert(this.client !== null);
84 | assert(isFunction(this.client.services.mode.remove), true);
85 |
86 | this.client.services.mode.remove({
87 | domain: 'example.com'
88 | }, (response) => {
89 |
90 | assert(response, true);
91 |
92 | });
93 |
94 | });
95 |
96 | describe('Mode.prototype.read()/failure', function(assert) {
97 |
98 | assert(this.client !== null);
99 | assert(isFunction(this.client.services.mode.read), true);
100 |
101 | this.client.services.mode.read({
102 | domain: 'example.com'
103 | }, (response) => {
104 |
105 | assert(response, null);
106 |
107 | });
108 |
109 | });
110 |
111 | after(disconnect_client);
112 | after(disconnect_stealth);
113 |
114 |
115 | export default finish('stealth/client/service/Mode', {
116 | internet: false,
117 | network: true
118 | });
119 |
120 |
--------------------------------------------------------------------------------
/browser/internal/debug/Browser.css:
--------------------------------------------------------------------------------
1 |
2 | @import url("../../design/card/Beacon.css");
3 | @import url("../../design/card/Host.css");
4 | @import url("../../design/card/Mode.css");
5 | @import url("../../design/card/Peer.css");
6 | @import url("../../design/card/Policy.css");
7 | @import url("../../design/card/Redirect.css");
8 | @import url("../../design/card/Session.css");
9 |
10 |
11 |
12 | browser-widget-browser {
13 | all: unset;
14 | display: block;
15 | position: relative;
16 | width: 800px;
17 | max-height: 64px;
18 | margin: 0px;
19 | padding: 16px;
20 | background: var(--surface-default-background);
21 | border: 1px solid var(--surface-default-color);
22 | border-radius: 8px;
23 | box-sizing: border-box;
24 | transition: 200ms all ease-out;
25 | overflow: hidden;
26 | cursor: default;
27 | }
28 |
29 | browser-widget-browser.active {
30 | max-height: unset;
31 | }
32 |
33 | browser-widget-browser > h3 {
34 | display: block;
35 | margin: 0px;
36 | padding: 0px 0px 16px 0px;
37 | font-family: 'crystalline';
38 | font-size: 32px;
39 | font-weight: 500;
40 | line-height: 32px;
41 | text-transform: uppercase;
42 | vertical-align: middle;
43 | }
44 |
45 | browser-widget-browser > button[data-action="toggle"] {
46 | display: block;
47 | position: absolute;
48 | top: 16px;
49 | right: 16px;
50 | bottom: auto;
51 | left: auto;
52 | }
53 |
54 | browser-widget-browser > button[data-action="toggle"]:before {
55 | content: '\e5c5';
56 | transition: 200ms transform ease-out;
57 | }
58 |
59 | browser-widget-browser > button[data-action="toggle"].active:before {
60 | transform: rotate(180deg);
61 | transition: 200ms transform ease-out;
62 | }
63 |
64 |
65 |
66 | browser-widget-browser-header {
67 | display: none;
68 | width: 100%;
69 | height: auto;
70 | }
71 |
72 | browser-widget-browser.active browser-widget-browser-header {
73 | display: block;
74 | }
75 |
76 | browser-widget-browser-header > input[data-key="domain"] {
77 | display: block;
78 | width: 100%;
79 | margin: 0px auto 16px auto;
80 | text-align: center;
81 | }
82 |
83 | browser-widget-browser-header > p {
84 | margin: 0px 0px 16px 0px;
85 | font-family: 'museo';
86 | font-size: 16px;
87 | text-align: center;
88 | }
89 |
90 |
91 |
92 | browser-widget-browser-article {
93 | display: none;
94 | width: 100%;
95 | height: auto;
96 | }
97 |
98 | browser-widget-browser.active browser-widget-browser-article {
99 | display: block;
100 | }
101 |
102 | browser-widget-browser-article > * {
103 | width: 100% !important;
104 | margin: 0px 0px 16px 0px !important;
105 | }
106 |
107 |
108 |
109 | browser-widget-browser-footer {
110 | display: none;
111 | height: 32px;
112 | margin: 16px 0px 0px 0px;
113 | padding: 0px;
114 | text-align: right;
115 | }
116 |
117 | browser-widget-browser.active browser-widget-browser-footer {
118 | display: block;
119 | }
120 |
121 | browser-widget-browser-footer > button[data-action="refresh"]:before { content: '\e5d5'; }
122 |
123 |
124 |
125 | @media screen and (max-width: 800px) {
126 |
127 | browser-widget-browser {
128 | width: auto;
129 | }
130 |
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/stealth/review/server/service/Blocker.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { isFunction } from '../../../../base/index.mjs';
3 | import { after, before, describe, finish } from '../../../../covert/index.mjs';
4 | import { Blocker } from '../../../../stealth/source/server/service/Blocker.mjs';
5 | import { connect, disconnect } from '../../../../stealth/review/Server.mjs';
6 |
7 |
8 |
9 | before(connect);
10 |
11 | describe('new Blocker()', function(assert) {
12 |
13 | assert(this.server !== null);
14 | assert(this.server.services.blocker instanceof Blocker, true);
15 |
16 | });
17 |
18 | describe('Blocker.prototype.toJSON()', function(assert) {
19 |
20 | assert(this.server !== null);
21 | assert(isFunction(this.server.services.blocker.toJSON), true);
22 |
23 | assert(this.server.services.blocker.toJSON(), {
24 | type: 'Blocker Service',
25 | data: {
26 | events: [],
27 | journal: []
28 | }
29 | });
30 |
31 | });
32 |
33 | describe('Blocker.isBlocker()', function(assert) {
34 |
35 | assert(isFunction(Blocker.isBlocker), true);
36 |
37 | assert(Blocker.isBlocker(null), false);
38 | assert(Blocker.isBlocker({}), false);
39 |
40 | assert(Blocker.isBlocker({
41 | domain: 'example.com'
42 | }), true);
43 |
44 | });
45 |
46 | describe('Blocker.toBlocker()', function(assert) {
47 |
48 | assert(isFunction(Blocker.toBlocker), true);
49 |
50 | assert(Blocker.toBlocker(null), null);
51 | assert(Blocker.toBlocker({}), null);
52 |
53 | assert(Blocker.toBlocker({
54 | domain: 'example.com',
55 | another: 'property'
56 | }), {
57 | domain: 'example.com'
58 | });
59 |
60 | });
61 |
62 | describe('Blocker.prototype.read()/success', function(assert) {
63 |
64 | assert(this.server !== null);
65 | assert(isFunction(this.server.services.blocker.read), true);
66 |
67 | this.server.services.blocker.read({
68 | domain: 'ads.quantserve.com'
69 | }, (response) => {
70 |
71 | assert(response, {
72 | headers: {
73 | service: 'blocker',
74 | event: 'read'
75 | },
76 | payload: {
77 | domain: 'ads.quantserve.com'
78 | }
79 | });
80 |
81 | });
82 |
83 | this.server.services.blocker.read({
84 | domain: 'subdomain.ads.quantserve.com'
85 | }, (response) => {
86 |
87 | assert(response, {
88 | headers: {
89 | service: 'blocker',
90 | event: 'read'
91 | },
92 | payload: {
93 | domain: 'ads.quantserve.com'
94 | }
95 | });
96 |
97 | });
98 |
99 | });
100 |
101 | describe('Blocker.prototype.read()/failure', function(assert) {
102 |
103 | assert(this.server !== null);
104 | assert(isFunction(this.server.services.blocker.read), true);
105 |
106 | this.server.services.blocker.read({
107 | domain: 'quantserve.com'
108 | }, (response) => {
109 |
110 | assert(response, {
111 | headers: {
112 | service: 'blocker',
113 | event: 'read'
114 | },
115 | payload: null
116 | });
117 |
118 | });
119 |
120 | });
121 |
122 | after(disconnect);
123 |
124 |
125 | export default finish('stealth/server/service/Blocker', {
126 | internet: false,
127 | network: true
128 | });
129 |
130 |
--------------------------------------------------------------------------------
/stealth/review/client/service/Beacon.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { isFunction } from '../../../../base/index.mjs';
3 | import { after, before, describe, finish } from '../../../../covert/index.mjs';
4 | import { Beacon } from '../../../../stealth/source/client/service/Beacon.mjs';
5 | import { connect as connect_stealth, disconnect as disconnect_stealth } from '../../../../stealth/review/Stealth.mjs';
6 | import { connect as connect_client, disconnect as disconnect_client } from '../../../../stealth/review/Client.mjs';
7 |
8 |
9 |
10 | before(connect_stealth);
11 | before(connect_client);
12 |
13 | describe('new Beacon()', function(assert) {
14 |
15 | assert(this.client !== null);
16 | assert(this.client.services.beacon instanceof Beacon, true);
17 |
18 | });
19 |
20 | describe('Beacon.prototype.toJSON()', function(assert) {
21 |
22 | assert(this.client !== null);
23 | assert(isFunction(this.client.services.beacon.toJSON), true);
24 |
25 | assert(this.client.services.beacon.toJSON(), {
26 | type: 'Beacon Service',
27 | data: {
28 | events: [],
29 | journal: []
30 | }
31 | });
32 |
33 | });
34 |
35 | describe('Beacon.prototype.save()', function(assert) {
36 |
37 | assert(this.client !== null);
38 | assert(isFunction(this.client.services.beacon.save), true);
39 |
40 | this.client.services.beacon.save({
41 | domain: 'example.com',
42 | beacons: [{
43 | path: '/index.html',
44 | query: null,
45 | select: 'body > article > h3',
46 | term: 'title'
47 | }]
48 | }, (response) => {
49 |
50 | assert(response, true);
51 |
52 | });
53 |
54 | });
55 |
56 | describe('Beacon.prototype.read()/success', function(assert) {
57 |
58 | assert(this.client !== null);
59 | assert(isFunction(this.client.services.beacon.read), true);
60 |
61 | this.client.services.beacon.read({
62 | domain: 'example.com'
63 | }, (response) => {
64 |
65 | assert(response, {
66 | domain: 'example.com',
67 | beacons: [{
68 | path: '/index.html',
69 | query: null,
70 | select: 'body > article > h3',
71 | term: 'title'
72 | }]
73 | });
74 |
75 | });
76 |
77 | });
78 |
79 | describe('Beacon.prototype.remove()', function(assert) {
80 |
81 | assert(this.client !== null);
82 | assert(isFunction(this.client.services.beacon.remove), true);
83 |
84 | this.client.services.beacon.remove({
85 | domain: 'example.com'
86 | }, (response) => {
87 |
88 | assert(response, true);
89 |
90 | });
91 |
92 | });
93 |
94 | describe('Beacon.prototype.read()/failure', function(assert) {
95 |
96 | assert(this.client !== null);
97 | assert(isFunction(this.client.services.beacon.read), true);
98 |
99 | this.client.services.beacon.read({
100 | domain: 'example.com'
101 | }, (response) => {
102 |
103 | assert(response, null);
104 |
105 | });
106 |
107 | });
108 |
109 | after(disconnect_client);
110 | after(disconnect_stealth);
111 |
112 |
113 | export default finish('stealth/client/service/Beacon', {
114 | internet: false,
115 | network: true
116 | });
117 |
118 |
--------------------------------------------------------------------------------
/browser/design/widget/Video.css:
--------------------------------------------------------------------------------
1 |
2 | browser-widget-video {
3 | all: unset;
4 | display: block;
5 | position: relative;
6 | width: 800px;
7 | margin: 0px;
8 | padding: 16px;
9 | background: var(--surface-default-background);
10 | border: 1px solid var(--surface-default-color);
11 | border-radius: 8px;
12 | box-sizing: border-box;
13 | transition: 200ms all ease-out;
14 | overflow: hidden;
15 | cursor: default;
16 | }
17 |
18 |
19 |
20 | browser-widget-video-article {
21 | display: block;
22 | position: relative;
23 | width: 100%;
24 | height: auto;
25 | text-align: center;
26 | overflow: hidden;
27 | z-index: 1;
28 | }
29 |
30 | browser-widget-video-article video {
31 | display: block;
32 | width: 100%;
33 | height: auto;
34 | max-height: 100%;
35 | margin: 0px auto;
36 | padding: 0px;
37 | }
38 |
39 |
40 |
41 | browser-widget-video-footer {
42 | display: block;
43 | height: 32px;
44 | margin: 16px 0px 0px 0px;
45 | padding: 0px;
46 | text-align: left;
47 | z-index: 2;
48 | }
49 |
50 | browser-widget-video-footer > button[data-action="play"],
51 | browser-widget-video-footer > button[data-action="pause"] {
52 | margin: 0px 16px 0px 0px;
53 | }
54 |
55 | browser-widget-video-footer > button[data-action="fullscreen"] {
56 | margin: 0px 0px 0px 16px;
57 | }
58 |
59 | browser-widget-video-footer > button[data-action="download"] {
60 | margin: 0px 0px 0px 16px;
61 | }
62 |
63 | browser-widget-video-footer > button[data-action="download"]:before { content: '\e2c4'; }
64 | browser-widget-video-footer > button[data-action="fullscreen"]:before { content: '\e5d0'; }
65 | browser-widget-video-footer > button[data-action="play"]:before { content: '\e037'; }
66 | browser-widget-video-footer > button[data-action="pause"]:before { content: '\e034'; }
67 |
68 | browser-widget-video-footer > progress {
69 | display: inline-block;
70 | width: calc(100% - 48px - 48px - 48px);
71 | height: 16px;
72 | line-height: 30px;
73 | border: 1px solid transparent;
74 | border-radius: 4px;
75 | box-sizing: border-box;
76 | appearance: none;
77 | -moz-appearance: none;
78 | -webkit-appearance: none;
79 | vertical-align: middle;
80 | cursor: col-resize;
81 | }
82 |
83 | browser-widget-video-footer > progress::-moz-progress-bar {
84 | display: inline-block;
85 | height: 100%;
86 | background: var(--element-focus-color);
87 | border-radius: 4px 0px 0px 4px;
88 | vertical-align: middle;
89 | box-sizing: border-box;
90 | }
91 |
92 | browser-widget-video-footer > progress[value="100"]::-moz-progress-bar {
93 | border-radius: 4px;
94 | }
95 |
96 | browser-widget-video-footer > progress::-webkit-progress-bar {
97 | background: var(--element-default-color);
98 | border-radius: 4px;
99 | }
100 |
101 | browser-widget-video-footer > progress::-webkit-progress-value {
102 | background: var(--element-focus-color);
103 | border-radius: 4px 0px 0px 4px;
104 | }
105 |
106 | browser-widget-video-footer > progress[value="100"]::-webkit-progress-value {
107 | border-radius: 4px;
108 | }
109 |
110 |
111 |
112 | @media screen and (max-width: 800px) {
113 |
114 | browser-widget-video {
115 | width: auto;
116 | }
117 |
118 | }
119 |
120 |
--------------------------------------------------------------------------------
/stealth/source/client/service/Cache.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { Buffer, Emitter, isFunction, isObject } from '../../../extern/base.mjs';
3 |
4 |
5 |
6 | const Cache = function(client) {
7 |
8 | this.client = client;
9 | Emitter.call(this);
10 |
11 | };
12 |
13 |
14 | Cache.prototype = Object.assign({}, Emitter.prototype, {
15 |
16 | toJSON: function() {
17 |
18 | let blob = Emitter.prototype.toJSON.call(this);
19 | let data = {
20 | events: blob.data.events,
21 | journal: blob.data.journal
22 | };
23 |
24 | return {
25 | 'type': 'Cache Service',
26 | 'data': data
27 | };
28 |
29 | },
30 |
31 | info: function(payload, callback) {
32 |
33 | payload = isObject(payload) ? payload : null;
34 | callback = isFunction(callback) ? callback : null;
35 |
36 |
37 | if (payload !== null && callback !== null) {
38 |
39 | this.once('info', (response) => callback(response));
40 |
41 | this.client.send({
42 | headers: {
43 | service: 'cache',
44 | method: 'info'
45 | },
46 | payload: payload
47 | });
48 |
49 | } else if (callback !== null) {
50 | callback(null);
51 | }
52 |
53 | },
54 |
55 | read: function(payload, callback) {
56 |
57 | payload = isObject(payload) ? payload : null;
58 | callback = isFunction(callback) ? callback : null;
59 |
60 |
61 | if (payload !== null && callback !== null) {
62 |
63 | this.once('read', (response) => {
64 |
65 | if (isObject(response) === true) {
66 |
67 | if (isObject(response.payload) === true) {
68 |
69 | if (response.payload.type === 'Buffer') {
70 | response.payload = Buffer.from(response.payload.data);
71 | }
72 |
73 | }
74 |
75 | }
76 |
77 | callback(response);
78 |
79 | });
80 |
81 | this.client.send({
82 | headers: {
83 | service: 'cache',
84 | method: 'read'
85 | },
86 | payload: payload
87 | });
88 |
89 | } else if (callback !== null) {
90 | callback(null);
91 | }
92 |
93 | },
94 |
95 | remove: function(payload, callback) {
96 |
97 | payload = isObject(payload) ? payload : null;
98 | callback = isFunction(callback) ? callback : null;
99 |
100 |
101 | if (payload !== null && callback !== null) {
102 |
103 | this.once('remove', (result) => callback(result));
104 |
105 | this.client.send({
106 | headers: {
107 | service: 'cache',
108 | method: 'remove'
109 | },
110 | payload: payload
111 | });
112 |
113 | } else if (callback !== null) {
114 | callback(false);
115 | }
116 |
117 | },
118 |
119 | save: function(payload, callback) {
120 |
121 | payload = isObject(payload) ? payload : null;
122 | callback = isFunction(callback) ? callback : null;
123 |
124 |
125 | if (payload !== null && callback !== null) {
126 |
127 | this.once('save', (result) => callback(result));
128 |
129 | this.client.send({
130 | headers: {
131 | service: 'cache',
132 | method: 'save'
133 | },
134 | payload: payload
135 | });
136 |
137 | } else if (callback !== null) {
138 | callback(false);
139 | }
140 |
141 | }
142 |
143 | });
144 |
145 |
146 | export { Cache };
147 |
148 |
--------------------------------------------------------------------------------
/browser/source/Session.mjs:
--------------------------------------------------------------------------------
1 |
2 | import { isArray, isNumber, isObject, isString } from '../extern/base.mjs';
3 | import { UA } from '../source/parser/UA.mjs';
4 | import { Tab } from '../source/Tab.mjs';
5 |
6 |
7 |
8 | export const isSession = function(obj) {
9 | return Object.prototype.toString.call(obj) === '[object Session]';
10 | };
11 |
12 |
13 |
14 | const Session = function() {
15 |
16 | this.agent = null;
17 | this.domain = Date.now() + '.tholian.network';
18 | this.stealth = null;
19 | this.tabs = [];
20 | this.warning = 0;
21 |
22 | };
23 |
24 |
25 | Session.from = function(json) {
26 |
27 | json = isObject(json) ? json : null;
28 |
29 |
30 | if (json !== null) {
31 |
32 | let type = json.type === 'Session' ? json.type : null;
33 | let data = isObject(json.data) ? json.data : null;
34 |
35 | if (type !== null && data !== null) {
36 |
37 | let session = new Session();
38 |
39 | if (UA.isUA(data.agent) === true) {
40 | session.agent = data.agent;
41 | }
42 |
43 | if (isString(data.domain) === true) {
44 | session.domain = data.domain;
45 | }
46 |
47 | if (isNumber(data.warning) === true) {
48 | session.warning = data.warning;
49 | }
50 |
51 | if (isArray(data.tabs) === true) {
52 | session.tabs = data.tabs.map((data) => Tab.from(data)).filter((tab) => tab !== null);
53 | }
54 |
55 | return session;
56 |
57 | }
58 |
59 | }
60 |
61 |
62 | return null;
63 |
64 | };
65 |
66 |
67 | Session.isSession = isSession;
68 |
69 |
70 | Session.merge = function(target, source) {
71 |
72 | target = isSession(target) ? target : null;
73 | source = isSession(source) ? source : null;
74 |
75 |
76 | if (target !== null && source !== null) {
77 |
78 | if (UA.isUA(source.agent) === true) {
79 | target.agent = source.agent;
80 | }
81 |
82 | if (isString(source.domain) === true) {
83 | target.domain = source.domain;
84 | }
85 |
86 | if (isArray(source.tabs) === true) {
87 |
88 | source.tabs.forEach((tab) => {
89 |
90 | let other = target.tabs.find((t) => t.id === tab.id) || null;
91 | if (other !== null) {
92 | Tab.merge(other, tab);
93 | } else {
94 | target.tabs.push(tab);
95 | }
96 |
97 | });
98 |
99 | }
100 |
101 | }
102 |
103 |
104 | return target;
105 |
106 | };
107 |
108 |
109 | Session.prototype = {
110 |
111 | [Symbol.toStringTag]: 'Session',
112 |
113 | toJSON: function() {
114 |
115 | let data = {
116 | agent: this.agent,
117 | domain: this.domain,
118 | tabs: this.tabs.map((tab) => tab.toJSON()),
119 | warning: this.warning
120 | };
121 |
122 | return {
123 | 'type': 'Session',
124 | 'data': data
125 | };
126 |
127 | },
128 |
129 | destroy: function() {
130 |
131 | this.tabs.forEach((tab) => {
132 | tab.destroy();
133 | });
134 |
135 |
136 | this.agent = null;
137 | this.domain = Date.now() + '.tholian.network';
138 | this.stealth = null;
139 | this.tabs = [];
140 | this.warning = 0;
141 |
142 |
143 | return true;
144 |
145 | }
146 |
147 | };
148 |
149 |
150 | export { Session };
151 |
152 |
--------------------------------------------------------------------------------
/browser/design/card/Session.css:
--------------------------------------------------------------------------------
1 |
2 | @import url("./Tab.css");
3 |
4 |
5 |
6 | browser-card-session {
7 | all: unset;
8 | display: block;
9 | position: relative;
10 | width: 800px;
11 | max-height: 64px;
12 | margin: 0px;
13 | padding: 16px;
14 | background: var(--surface-default-background);
15 | border: 1px solid var(--surface-default-color);
16 | border-radius: 8px;
17 | box-sizing: border-box;
18 | transition: 200ms all ease-out;
19 | overflow: hidden;
20 | cursor: default;
21 | }
22 |
23 | browser-card-session.active {
24 | max-height: unset;
25 | }
26 |
27 | browser-card-session > h3 {
28 | display: block;
29 | height: 32px;
30 | margin: 0px 0px 16px 0px;
31 | padding: 0px;
32 | line-height: 32px;
33 | font-family: 'vera-mono';
34 | font-size: 16px;
35 | font-weight: 400;
36 | text-transform: uppercase;
37 | vertical-align: middle;
38 | }
39 |
40 | browser-card-session > h3:before {
41 | display: inline;
42 | content: '\e335';
43 | margin: 0px 16px 0px 0px;
44 | padding: 0px;
45 | font-family: 'icon';
46 | font-size: 24px;
47 | font-weight: 400;
48 | text-align: center;
49 | vertical-align: top;
50 | speak: none;
51 | -webkit-font-smoothing: antialiased;
52 | }
53 |
54 | browser-card-session > h3 span {
55 | font-size: 16px;
56 | text-transform: none;
57 | }
58 |
59 | browser-card-session > button[data-action="toggle"] {
60 | display: block;
61 | position: absolute;
62 | top: 16px;
63 | right: 16px;
64 | bottom: auto;
65 | left: auto;
66 | }
67 |
68 | browser-card-session > button[data-action="toggle"]:before {
69 | content: '\e5c5';
70 | transition: 200ms transform ease-out;
71 | }
72 |
73 | browser-card-session > button[data-action="toggle"].active:before {
74 | transform: rotate(180deg);
75 | transition: 200ms transform ease-out;
76 | }
77 |
78 |
79 |
80 | browser-card-session-header {
81 | display: none;
82 | width: 100%;
83 | height: auto;
84 | margin: 0px 0px 16px 0px;
85 | }
86 |
87 | browser-card-session.active browser-card-session-header {
88 | display: block;
89 | }
90 |
91 | browser-card-session-header > code {
92 | display: inline-block;
93 | margin: 0px;
94 | padding: 4px;
95 | font-family: 'vera-mono';
96 | color: var(--layout-default-color);
97 | background: var(--layout-default-background);
98 | border-radius: 4px;
99 | }
100 |
101 |
102 |
103 | browser-card-session-article {
104 | display: none;
105 | width: 100%;
106 | height: auto;
107 | }
108 |
109 | browser-card-session.active browser-card-session-article {
110 | display: block;
111 | }
112 |
113 | browser-card-session-article > browser-card-tab {
114 | width: 100%;
115 | margin: 0px 0px 16px 0px;
116 | }
117 |
118 |
119 |
120 | browser-card-session-footer {
121 | display: none;
122 | height: 32px;
123 | margin: 16px 0px 0px 0px;
124 | padding: 0px;
125 | text-align: right;
126 | }
127 |
128 | browser-card-session.active browser-card-session-footer {
129 | display: block;
130 | }
131 |
132 | browser-card-session-footer > button[data-action="remove"]:before { content: '\e14c'; }
133 |
134 |
135 |
136 | @media screen and (max-width: 800px) {
137 |
138 | browser-card-settings {
139 | width: auto;
140 | }
141 |
142 | }
143 |
144 |
--------------------------------------------------------------------------------
/browser/design/index.css:
--------------------------------------------------------------------------------
1 |
2 | @import url("./common/index.css");
3 | @import url("./common/theme.css");
4 |
5 | @import url("./appbar/Address.css");
6 | @import url("./appbar/History.css");
7 | @import url("./appbar/Mode.css");
8 | @import url("./appbar/Settings.css");
9 | @import url("./appbar/Splitter.css");
10 |
11 | @import url("./backdrop/Tabs.css");
12 | @import url("./backdrop/Webview.css");
13 |
14 | @import url("./sheet/Console.css");
15 | @import url("./sheet/Session.css");
16 | @import url("./sheet/Site.css");
17 |
18 | @import url("./menu/Context.css");
19 | @import url("./menu/Help.css");
20 |
21 |
22 |
23 | /*
24 | * App Layers
25 | */
26 |
27 | base-console { z-index: 255 !important; }
28 | base-console.active ~ browser-appbar { z-index: 254 !important; }
29 |
30 | browser-appbar { z-index: 512; }
31 | browser-appbar browser-appbar-history { z-index: unset; }
32 | browser-appbar browser-appbar-address { z-index: unset; }
33 | browser-appbar browser-appbar-mode { z-index: unset; }
34 | browser-appbar browser-appbar-settings { z-index: unset; }
35 | browser-appbar button[data-key="splitter"] { z-index: 256; } /* only on mobile */
36 | browser-appbar browser-appbar-splitter { z-index: 255; } /* only on mobile */
37 | browser-backdrop { z-index: 0; }
38 | browser-backdrop browser-backdrop-webview { z-index: 1; }
39 | browser-backdrop browser-backdrop-tabs { z-index: 128; }
40 | browser-sheet { z-index: 256; }
41 | browser-sheet browser-sheet-session { z-index: 257; }
42 | browser-sheet browser-sheet-site { z-index: 257; }
43 | browser-sheet browser-sheet-console { z-index: 511; } /* only on ?debug=true */
44 | browser-menu { z-index: 1024; }
45 | browser-menu browser-menu-context { z-index: 1025; }
46 | browser-menu browser-menu-help { z-index: 1026; }
47 |
48 |
49 | body {
50 | display: block;
51 | margin: 0px;
52 | padding: 0px;
53 | color: var(--layout-default-color);
54 | background: var(--layout-default-background);
55 | }
56 |
57 | browser-appbar {
58 | display: block;
59 | position: fixed;
60 | width: auto;
61 | height: auto;
62 | height: 33px;
63 | line-height: 32px;
64 | top: 0px;
65 | right: 0px;
66 | bottom: auto;
67 | left: 0px;
68 | margin: 0px;
69 | padding: 0px;
70 | border: 0px solid transparent;
71 | border-bottom: 1px solid var(--surface-default-color);
72 | background: var(--surface-default-background);
73 | box-sizing: border-box;
74 | vertical-align: middle;
75 | }
76 |
77 | browser-backdrop {
78 | display: block;
79 | position: relative;
80 | }
81 |
82 | browser-backdrop-tabs {
83 | top: calc(32px + 1px);
84 | }
85 |
86 | browser-backdrop-webview {
87 | top: calc(32px + 1px);
88 | }
89 |
90 | browser-sheet {
91 | display: block;
92 | position: relative;
93 | }
94 |
95 | browser-menu {
96 | display: block;
97 | position: relative;
98 | }
99 |
100 |
101 |
102 | @media screen and (max-width: 412px) {
103 |
104 | browser-backdrop-tabs {
105 | top: calc(64px + 1px);
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------