Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/t2ym/thin-hook

Thin Hook Preprocessor
https://github.com/t2ym/thin-hook

access-control-list es6 hooks mandatory-access-control service-worker

Last synced: 19 days ago
JSON representation

Thin Hook Preprocessor

Awesome Lists containing this project

README

        

[![npm version](https://badge.fury.io/js/thin-hook.svg)](https://badge.fury.io/js/thin-hook)
[![Bower version](https://badge.fury.io/bo/thin-hook.svg)](https://badge.fury.io/bo/thin-hook)

# thin-hook

Thin Hook Preprocessor (experimental)

## Notes

- **[Vulnerability Fix]** Since [0.4.0-alpha.45](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.45) with [Fix #398 Unchain policy objects to `Object.prototype`](https://github.com/t2ym/thin-hook/issues/398), access policy objects are immune to contaminated `Object.prototype` properties when `targetConfig.policy.unchainAcl = true` is configured (which is disabled by default for compatibility). Prior to this version, writable `Object.prototype` properties can contaminate access policy objects.
- **[Vulnerability Fix]** Since [0.4.0-alpha.45](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.45) with [Fix #399 Recognize acl for `Object.prototype` object properly](https://github.com/t2ym/thin-hook/issues/399), `acl.Object[S_PROTOTYPE]` policies for `Object.prototype` properties are properly applied when `targetConfig.policy.unchainAcl = true` is configured (which is disabled by default for compatibility). Prior to this version, `acl.Object` policies are incorrectly applied for `Object.prototype` properties in some cases.
- **[Enhancement]** Since [0.4.0-alpha.28](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.28) with [Issue #376 Support ES modules](https://github.com/t2ym/thin-hook/issues/376), ACL policies are applied to ES modules by hooking ES module objects. This feature is optional and can be disabled by `hook.parameters.importMapper = null` in `demo/bootstrap.js`
- **[Vulnerability Fix]** Since [0.4.0-alpha.25](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.25) with [Fix #369 Block DOM intrusion by Browser Extensions](https://github.com/t2ym/thin-hook/issues/369), the application hangs up with an alert message on DOM intrusion by Browser Extensions. Prior to this version, Browser Extensions can intrude into DOM and manipulate contents.
- **[Vulnerability Fix]** Since [0.4.0-alpha.24](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.24) with [Fix #368 Check Service Worker cache integrity](https://github.com/t2ym/thin-hook/issues/368), integrity of Service Worker cache contents is verified with HMAC keys. Prior to this version, corrupted Service Worker cache contents can intrude into the application.
- **[Vulnerability Fix]** Since [0.4.0-alpha.22](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.22) with [Fix #363 Block blob URLs](https://github.com/t2ym/thin-hook/issues/363), blob URLs are blocked except for `Download Link`. Prior to this version, documents with blob URLs bypass Service Worker.
- **[Vulnerability Fix]** Since [0.4.0-alpha.22](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.22) with [Fix #362 Option to block `` and `` elements](https://github.com/t2ym/thin-hook/issues/362), the application hangs up on `` and `` activities with `hook.parameters.hangUpOnEmbedAndObjectElement = true`. Prior to this version, `` and `` documents can bypass Service Worker with Chrome Canary 86.
- **[Vulnerability Fix]** Since [0.4.0-alpha.21](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.21) with [Fix #355 Treat proxy objects as alias objects in ACL](https://github.com/t2ym/thin-hook/issues/355), ACL is properly applied for proxy objects created via `new Proxy(target, handler)` and `Proxy.revocable(target, handler)` as with their original `target` objects. Prior to this version, ACL for the `target` objects are not applied to proxy objects.
- **[Vulnerability Fix]** Since [0.4.0-alpha.20](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.20) with [Fix #350 Append the target value to hooked arguments and pick it up in `Policy.defaultAcl()`](https://github.com/t2ym/thin-hook/issues/350), ACL is properly applied for `with`-scoped values in function calls and constructor calls. Prior to this version, calls to `with`-scoped functions can skip ACLs for their reference values.
- **[Vulnerability Fix]** Since [0.4.0-alpha.20](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.20) with [Fix #349 Hook local function calls in `with` clause](https://github.com/t2ym/thin-hook/issues/349), local function calls in `with` clause are property hooked. Prior to this version, local function calls in `with` clause are not hooked. This is a regression issue from the fix for [Fix #339 Local variables in a with block are mistreated as global variables in ACL #339](https://github.com/t2ym/thin-hook/issues/339).
- **[Vulnerability Fix]** Since [0.4.0-alpha.20](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.20) with [Fix #348 Hook tagged template literals as function calls](https://github.com/t2ym/thin-hook/issues/348), tag functions of tagged template literals are hooked as function calls. Prior to this version, calls to tag functions of tagged template literals are not hooked.
- **[Vulnerability Fix]** Since [0.4.0-alpha.19](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.19) with [Fix #347 Apply ACL for properties of primitive values](https://github.com/t2ym/thin-hook/issues/347), `acl.type[S_PROTOTYPE][S_INSTANCE]` ACLs for primitive type classes `String`, `Number`, `Boolean`, `Symbol`, and `BigInt` are applied to properties of primitive values. Prior to this version, ACL is not applied to properties of primitive values.
- **[Vulnerability Fix]** Since [0.4.0-alpha.17](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.17) with [Fix #344 Normalize `with` namespace objects before bound function detection](https://github.com/t2ym/thin-hook/issues/344), bound function calls in a `with` clause is properly detected. Prior to this version, ACL for unbound original function is not applied for bound function calls in a `with` clause.
- **[Vulnerability Fix]** Since [0.4.0-alpha.16](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.16) with [Fix #342 Chain `acl[S_DEFAULT][S_PROTOTYPE][S_INSTANCE]` to `acl.Object[S_PROTOTYPE][S_INSTANCE]`](https://github.com/t2ym/thin-hook/issues/342), `acl.Object[S_PROTOTYPE][S_INSTANCE]` is applied for anonymous object properties. Prior to this version, `acl.Object[S_PROTOTYPE][S_INSTANCE]` is not applied anonymous object properties.
- **[Vulnerability Fix]** Since [0.4.0-alpha.15](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.15) with [Fix #341 Apply ACL to all sources of Object.assign()](https://github.com/t2ym/thin-hook/issues/341), ACL is applied for all sources of `Object.assign()` even they contain falsy values. Prior to this version, ACL is not applied for sources of `Object.assign()` if the first source is not an object like `undefined`.
- **[Vulnerability Fix]** Since [0.4.0-alpha.13](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.13) with [Fix #336 Apply ACL for super classes/objects of global classes/objects with no dedicated ACLs](https://github.com/t2ym/thin-hook/issues/336), ACL is applied for super classes/objects of global classes/objects and instances of global classes even without dedicated ACLs. Prior to this version, ACL is not applied for super classes/objects of global classes/objects and instances of global classes without dedicated ACLs.
- **[Vulnerability Fix]** Since [0.4.0-alpha.9](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.9) with [Fix #336 Apply ACL for super classes/objects](https://github.com/t2ym/thin-hook/issues/336), ACL is applied for super classes/objects of non-global classes/objects and instances of non-global classes. Prior to this version, ACL is not applied for super classes/objects of non-global classes/objects and instances of non-global classes.
- **[Vulnerability Fix]** Since [0.4.0-alpha.8](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.8) with [Fix #334 Apply ACL for reading the target property of `receiver` in `Reflect.get()`](https://github.com/t2ym/thin-hook/issues/334), ACL is applied for `receiver` in `Reflect.get()` to read the target property. Prior to this version, ACL is not applied for `receiver` in `Reflect.get()`.
- **[Vulnerability Fix]** Since [0.4.0-alpha.7](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.7) with [Fix #333 Check constructors of Object.assign() source objects](https://github.com/t2ym/thin-hook/issues/333), ACL is applied for class instances as source objects in `Object.assign()` by checking their constructors. Prior to this version, ACL for class instances as source objects in `Object.assign()` is not applied while ACL for global objects is applied properly. This fix is to supplement the fix for [Fix #324 Apply ACL for S_TARGETED normalized properties with S_ALL normalized property](https://github.com/t2ym/thin-hook/issues/324).
- **[Vulnerability Fix]** Since [0.4.0-alpha.6](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.6) with [Fix #331 Check for all property access of destructured argument objects](https://github.com/t2ym/thin-hook/issues/331), destructured argument objects of functions are checked against all property access when called. Prior to this version, all properties of destructured argument objects of functions can be read without S_ALL access.
- **[Vulnerability Fix]** Since [0.4.0-alpha.5](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.5) with [Fix #327 Hook RHS values of ObjectPattern and ArrayPattern for checking all property access](https://github.com/t2ym/thin-hook/issues/327), RHS values of ObjectPattern and ArrayPattern are hooked for checking all property access of the target values. Prior to this version, all properties of RHS values in ObjectPattern and ArrayPattern can be iterated without S_ALL access.
- **[Vulnerability Fix]** Since [0.4.0-alpha.4](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.4) with [Fix #325 Track global objects set via defineProperty(), etc.](https://github.com/t2ym/thin-hook/issues/325), global objects set via `Object.defineProperty()`, etc. are properly tracked for ACL. Prior to this version, ACL is skipped for global objects set via `Object.defineProperty()`, etc.
- **[Vulnerability Fix]** Since [0.4.0-alpha.3](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.3) with [Fix #324 Apply ACL for S_TARGETED normalized properties with S_ALL normalized property](https://github.com/t2ym/thin-hook/issues/324), source objects in `Object.assign()` are checked against ACL for reading all properties. Prior to this version, ACL is skipped for source objects in `Object.assign()` and any enumerable properties in the source objects can be assigned to the target object.
- **[Vulnerability Fix]** Since [0.4.0-alpha.2](https://github.com/t2ym/thin-hook/releases/tag/0.4.0-alpha.2) with [Fix #310 Hang up with an infinite loop if a hacked Service Worker tries to replace the entry page](https://github.com/t2ym/thin-hook/issues/310), the entry page hangs up if a hacked Service Worker tries to navigate the entry page. Prior to this version, a hacked Service Worker from an MITM attacker can replace the entry page with an arbitrary URL.
- **[Vulnerability Fix]** Since [0.3.7](https://github.com/t2ym/thin-hook/releases/tag/0.3.7) with [Fix #319 Check HTML/SVG extensions and content-type case-insensitively](https://github.com/t2ym/thin-hook/issues/319), extensions and content-type for HTML and SVG are checked case-insensitively. Prior to this version, extensions and content-type for HTML and SVG are check case-sensitively and some contents with capital letters such as *.HTML with content-type TEXT/HTML are not detected as HTML and can bypass hooking.
- **[Vulnerability Fix]** Since [0.3.6](https://github.com/t2ym/thin-hook/releases/tag/0.3.6) with [Fix #318 Check Worker extensions](https://github.com/t2ym/thin-hook/issues/318), extensions for Worker URLs are checked against .m?js. Prior to this version, extensions for Worker URLs are not checked and workers with irregular extensions can bypass hooking.
- **[Vulnerability Fix]** Since [0.3.5](https://github.com/t2ym/thin-hook/releases/tag/0.3.5) with [Fix #316 Redirect top SVG to about:blank](https://github.com/t2ym/thin-hook/issues/316), top SVG document is redirected to about:blank. Prior to this version, top SVG document can invalidate disable-devtools.js and DevTools is unexpectedly enabled.
- **[Vulnerability Fix]** Since [0.3.4](https://github.com/t2ym/thin-hook/releases/tag/0.3.4) with [Fix #314 Check content-type for HTML and SVG as well as extensions](https://github.com/t2ym/thin-hook/issues/314), content-type is checked for HTML/SVG detection as well as extensions. Prior to this version, HTML/SVG responses with irregular extensions are not detected as HTML/SVG and thus not hooked.
- **[Vulnerability Fix]** Since [0.3.3](https://github.com/t2ym/thin-hook/releases/tag/0.3.3) with [Fix #313 GET errorReport.json with 307 about:blank response](https://github.com/t2ym/thin-hook/issues/313), 307 redirect to `about:blank` is responded for GET errorReport.json request. Prior to this version, 404 Not Found is responded for GET errorReport.json, whose HTML contents in iframe can be accessed bypassing access policies.
- **[Feature Enhancements]** Since [0.3.0](https://github.com/t2ym/thin-hook/releases/tag/0.3.0) with [Fix #284 Additional Content-Types in cache-bundle.json](https://github.com/t2ym/thin-hook/issues/284), extended metadata are supported in `cache-bundle.json` to add additional cacheable content types. This README document is updated to describe the new features and their configurations.
- **[Feature Enhancements]** Since [0.2.0](https://github.com/t2ym/thin-hook/releases/tag/0.2.0) with [Fix #266 Block access via automation like puppeteer](https://github.com/t2ym/thin-hook/issues/266), there are many significant changes on global object access and hooking. ACL is basically compatible with prior versions but extra configurations for new features are required. This README document is updated to describe the new features and their configurations.
- **[Vulnerability Fix]** Since [0.1.11](https://github.com/t2ym/thin-hook/releases/tag/0.1.11) with [Fix #265 Attach context to wrapper property name for global object access](https://github.com/t2ym/thin-hook/issues/265), the correct contexts are used for global object access in self-assignment. Prior to this version, the context for the RHS value in self-assignment is incorrectly used for the access to the object.
- **[Vulnerability Fix]** Since [0.1.9](https://github.com/t2ym/thin-hook/releases/tag/0.1.9) with [Fix #263 Use the current context for global object access](https://github.com/t2ym/thin-hook/issues/263), the correct contexts are used for global object read/write/call access. Prior to this version, the context for the first access to the target global object is incorrectly used for the following access to the object.
- **[Configuration]** Since [0.0.250](https://github.com/t2ym/thin-hook/releases/tag/0.0.250) with [Fix #252 Block direct access to source codes](https://github.com/t2ym/thin-hook/issues/252) and [Fix #254 Block direct access to source codes even after the app shutdown](https://github.com/t2ym/thin-hook/issues/254), direct access to source codes are blocked. `hook.parameters.appPathRoot = '/';` in `demo/disable-devtools.js` can be configured to set the root of the application assets. Prior to this version, direct access to source codes are allowed.
- **[Vulnerability Fix]** Since [0.0.243](https://github.com/t2ym/thin-hook/releases/tag/0.0.243) with [Fix #250 Hook scripts in SVG and block data:/blob: URLs for SVG](https://github.com/t2ym/thin-hook/issues/250), scripts in SVG are hooked and `blob:` and `data:` URLs are blocked for SVG. Prior to this version, scripts in SVG are not hoooked and `blob:` and `data:` URLs are allowed for SVG. ``, ``, ``
- **[Vulnerability Fix]** Since [0.0.239](https://github.com/t2ym/thin-hook/releases/tag/0.0.239) with [Fix #249 Block blob: URLs for Worker](https://github.com/t2ym/thin-hook/issues/249), `blob:` and `data:` URLs are blocked for `Worker` and `SharedWorker`. Prior to this version, `blob:` and `data:` URLs are allowed for `Worker` and `SharedWorker`.
- **[Vulnerability Fix]** Since [0.0.236](https://github.com/t2ym/thin-hook/releases/tag/0.0.236) with [Fix #247 Hook script.text property](https://github.com/t2ym/thin-hook/issues/247), script.text property is properly hooked. Prior to this version, script.text property is not hooked.
- **[Vulnerability Fix]** Since [0.0.236](https://github.com/t2ym/thin-hook/releases/tag/0.0.236) with [Fix #246 Handle non-http protocols in iframe.src, script.src properly](https://github.com/t2ym/thin-hook/issues/246), non-http protocols in iframe src and script src are handled properly. Prior to this version, non-http protocols in iframe src and script src are not handled properly.
- **[Vulnerability Fix]** Since [0.0.235](https://github.com/t2ym/thin-hook/releases/tag/0.0.235) with [Fix #245 no-hook-authorization parameter is missing in sub documents](https://github.com/t2ym/thin-hook/issues/245), unauthorized no hook scripts are blocked in sub documents. Prior to this version, unauthorized no hook scripts in sub documents are not blocked.
- **[Vulnerability Fix]** Since [0.0.233](https://github.com/t2ym/thin-hook/releases/tag/0.0.233) with [Fix #242 Hook iframe.srcdoc](https://github.com/t2ym/thin-hook/issues/242), `iframe.srcdoc` is hooked as `onload` attribute. Prior to this version, `iframe.srcdoc` is not hooked.
- **[Vulnerability Fix]** Since [0.0.232](https://github.com/t2ym/thin-hook/releases/tag/0.0.232) with [Fix #241 AsyncFunction() is not hooked](https://github.com/t2ym/thin-hook/issues/241), `AsyncFunction('script')` is properly hooked. Prior to this version, `AsyncFunction('script')` is not hooked. `AsyncFunction = (async function() {}).constructor`
- **[Vulnerability Fix]** Since [0.0.231](https://github.com/t2ym/thin-hook/releases/tag/0.0.231) with [Fix #240 object.Function() is not hooked](https://github.com/t2ym/thin-hook/issues/240), `object.Function('script')` is properly hooked. Prior to this version, `object.Function('script')` is not hooked.
- **[Vulnerability Fix]** Since [0.0.230](https://github.com/t2ym/thin-hook/releases/tag/0.0.230) with [Fix #239 Full ACLs for iframe.contentWindow](https://github.com/t2ym/thin-hook/issues/239), full ACLs for iframe.contentWindow are properly applied. Prior to this version, only partial ACLs for iframe.contentWindow are applied.
- **[Vulnerability Fix]** Since [0.0.229](https://github.com/t2ym/thin-hook/releases/tag/0.0.229) with [Fix #238 No ACLs for iframe.contentWindow](https://github.com/t2ym/thin-hook/issues/238), global object ACLs for iframe.contentWindow are properly applied. Prior to this version, global object ACLs for iframe.contentWindow are not applied.
- **[Vulnerability Fix]** Since [0.0.228](https://github.com/t2ym/thin-hook/releases/tag/0.0.228) with [Fix #234 Global ACLs are not applied in web workers](https://github.com/t2ym/thin-hook/issues/234), ACLs for global objects in web workers are properly applied. Prior to this version, ACLs for global objects in web workers are not applied.
- **[Performance Optimization]** `__hook__acl` in `demo/hook-callback.js` should be used as it is much faster than `__hook__` as described in [Fix #230](https://github.com/t2ym/thin-hook/issues/230). Modification: `Object.defineProperty(_global, '__hook__', { configurable: false, enumerable: false, writable: false, value: hookCallbacks.__hook__acl });`
- **[ACL Compatibility]** Since [0.0.225](https://github.com/t2ym/thin-hook/releases/tag/0.0.225) with [Fix #229 Exclude Multiple ACLs for global object properties](https://github.com/t2ym/thin-hook/issues/229), ACLs for the global object properties (`top`, `parent`, `frames`, `global`, `_global`, etc.) other than the main global object property (`window` in the main document, `self` in workers) are applied only for access like `window.top`. In 0.0.224, all the ACLs for the global object properties are applied for every global object access, which is redundant.
- **[Vulnerability Fix]** Since [0.0.225](https://github.com/t2ym/thin-hook/releases/tag/0.0.224) with [Fix #227 Private API registered in strict mode](https://github.com/t2ym/thin-hook/issues/227), ACLs for private APIs registered to the global object in strict mode are properly applied. Prior to this version, ACLs for private APIs registered to the global object in strict mode are not applied.
- **[ACL Compatibility]** Since [0.0.225](https://github.com/t2ym/thin-hook/releases/tag/0.0.225) with [Fix #226 Multiple ACLs](https://github.com/t2ym/thin-hook/issues/226), `_globalObjects` is a `SetMap` object defined in `hook-callback.js` and `_globalObjects.get(obj)` return a `Set` object containing `string`s. All the ACLs for the set of `string`s are applied for the object. Prior to this version, `_globalObjects` is a `Map` object and `_globalObjects.get(obj)` returns a `string`.
- **[ACL Compatibility]** Since [0.0.225](https://github.com/t2ym/thin-hook/releases/tag/0.0.225) with [Fix #226 Multiple ACLs](https://github.com/t2ym/thin-hook/issues/226), `_blacklistObjects` is deprecated.
- **[ACL Compatibility]** Since [0.0.216](https://github.com/t2ym/thin-hook/releases/tag/0.0.216) with [Fix #217](https://github.com/t2ym/thin-hook/issues/217), `delete` operations require `'W'` permission as they can delete properties with customized descriptors. Prior to this version, `delete` operations require `'w'` permission.
- **[ACL Compatibility]** Since [0.0.214](https://github.com/t2ym/thin-hook/releases/tag/0.0.214) with [Fix #215](https://github.com/t2ym/thin-hook/issues/215), `'R'` and `'W'` opTypes are introduced for getting/setting property descriptors, i.e., contexts to access descriptors must have explicit `'R'` and/or `'W'` permissions for the target properties. Prior to [0.0.213](https://github.com/t2ym/thin-hook/releases/tag/0.0.213), property descriptors can be accessed by mere `'r'` and/or `'w'` permissions.
- **[Vulnerability Fix]** Since [0.0.211](https://github.com/t2ym/thin-hook/releases/tag/0.0.211) with [Fix #211](https://github.com/t2ym/thin-hook/issues/211), bypassing of ACL for global objects by dummy custom element definition is avoided. Prior to this version, ACL can be skipped by defining dummy custom elements by standard elements as constructor classes.
- **[Vulnerability Fix]** Since [0.0.209](https://github.com/t2ym/thin-hook/releases/tag/0.0.209) with [Fix #210](https://github.com/t2ym/thin-hook/issues/210), bypassing of ACL for global objects by cloing them to other global objects is avoided. Prior to this version, ACL can be skipped by cloing global objects.
- **[Vulnerability Fix]** Since [0.0.205](https://github.com/t2ym/thin-hook/releases/tag/0.0.205) with [Fix #208](https://github.com/t2ym/thin-hook/issues/208), scripts via `document.writeln()` are hooked as in `document.write()`. Prior to this version, scripts via `document.writeln()` are not hooked.
- **[Vulnerability Fix]** Since [0.0.203](https://github.com/t2ym/thin-hook/releases/tag/0.0.203) with [Fix #207](https://github.com/t2ym/thin-hook/issues/207), `textContent` of `script` elements are always treated as JavaScript scripts regardless of their configured MIME types (`type` property/attribute). Prior to this version, `textContent` of `script` elements containing `__hook__` as strings can be mistaken as **HOOKED** scripts and run without hooking.
- **[Context Generator Compatibility]** Since [0.0.148](https://github.com/t2ym/thin-hook/releases/tag/0.0.148) with [#144](https://github.com/t2ym/thin-hook/issues/144), the old context generator `"method"` is renamed as `"oldMethod"` and the `"cachedMethod"` is renamed as `"method"` and become the new default context generator. The `"cachedMethod"` remains as an alias for the new `"method"` context generator. There are slight changes in the new `"method"` context generator. A warning message is shown on the debug console to notify the change.

| old name | new name | feature |
|:-----:|:-----:|:-----|
| `method` | `oldMethod` | `script.js,Class,method` |
| `cachedMethod` | `method` | `script.js,Class,method` including computed property names |
- **[Hook Callback Compatibility]** Since [0.0.149](https://github.com/t2ym/thin-hook/releases/tag/0.0.149) with [#123](https://github.com/t2ym/thin-hook/issues/123), the hook callback function has to support new operators for hooking in strict mode. See below for the updated hook callback function `hook.__hook__`. `hook.hookCallbackCompatibilityTest()` can detect if the target hook callback function is compatible or not.
- **[Opaque URL Authorization]** Since [0.0.178](https://github.com/t2ym/thin-hook/releases/tag/0.0.178) with [#178](https://github.com/t2ym/thin-hook/issues/178), all opaque content URLs must be authorized via `hook.parameters.opaque = [ 'opaque_url', ..., (url) => url.match(/opaque_url_pattern/), ... ]` configuration.

### Native API Access Graph generated via hook callback function (view2 of thin-hook/demo/)

[Demo](https://t2ym.github.io/thin-hook/components/thin-hook/demo/index.html) on GitHub Pages

### Input
```javascript
class C {
add(a = 1, b = 2) {
let plus = (x, y) => x + y;
return plus(a, b);
}
}
```

### Hooked Output
```javascript
const __context_mapper__ = $hook$.$(__hook__, [
'examples/example2.js,C',
'_p_C;examples/example2.js,C',
'examples/example2.js,C,add',
'examples/example2.js,C,add,plus'
]);
$hook$.global(__hook__, __context_mapper__[0], 'C', 'class')[__context_mapper__[1]] = class C {
add(a, b) {
return __hook__((a = 1, b = 2) => {
let plus = (...args) => __hook__((x, y) => x + y, null, args, __context_mapper__[3]);
return __hook__(plus, null, [
a,
b
], __context_mapper__[2], 0);
}, null, arguments, __context_mapper__[2]);
}
};
```

### Preprocess

```javascript
const hook = require('thin-hook/hook.js');
let code = fs.readFileSync('src/target.js', 'UTF-8');
let initialContext = [['src/target.js', {}]];
let gen = hook(code, '__hook__', initialContext, 'hash');
fs.writeFileSync('hooked/target.js', gen);
fs.writeFileSync('hooked/target.js.contexts.json', JSON.stringify(contexts, null, 2));
```

### Context Generator Function (customizable)

```javascript
// Built-in Context Generator Function
hook.contextGenerators.method = function generateMethodContext(astPath) {
return astPath.map(([ path, node ], index) => node && node.type
? (node.id && node.id.name ? node.id.name : (node.key && node.key.name
? (node.kind === 'get' || node.kind === 'set' ? node.kind + ' ' : node.static ? 'static ' : '') + node.key.name : ''))
: index === 0 ? path : '').filter(p => p).join(',');
}
```

```javascript
// Example Custom Context Generator Function with Hashing
const hashSalt = '__hash_salt__';
let contexts = {};

hook.contextGenerators.hash = function generateHashContext(astPath) {
const hash = hook.utils.createHash('sha256');
let hashedInitialContext = astPath[0][0];
astPath[0][0] = contexts[hashedInitialContext] || astPath[0][0];
let methodContext = hook.contextGenerators.method(astPath);
astPath[0][0] = hashedInitialContext;
hash.update(hashSalt + methodContext);
let hashContext = hash.digest('hex');
contexts[hashContext] = methodContext;
return hashContext;
}
```

```javascript
{
// Authorization Tickets for no-hook scripts
// Ticket for this script itself is specified in URL of script tag as
// hook.min.js?no-hook-authorization={ticket}
// Note: no-hook-authorization must not exist in learning mode
let noHookAuthorization = {
// '*' is for learning mode to detect authorization tickets in
// hook.parameters.noHookAuthorizationPassed,
// hook.parameters.noHookAuthorizationFailed
// JSONs are output to console in the learning mode
//'*': true,
"35ae97a3305b863af7eb0ac75c8679233a2a7550e4c3046507fc9ea182c03615": true,
"16afd3d5aa90cbd026eabcc4f09b1e4207a7042bc1e9be3b36d94415513683ed": true,
"ae11a06c0ddec9f5b75de82a40745d6d1f92aea1459e8680171c405a5497d1c8": true,
"5b7ebf7b0b2977d44f47ffa4b19907abbc443feb31c343a6cbbbb033c8deb01a": true,
"c714633723320be54f106de0c50933c0aeda8ac3fba7c41c97a815ed0e71594c": true,
"2f43d927664bdfcbcb2cc4e3743652c7eb070057efe7eaf43910426c6eae7e45": true,
"b397e7c81cca74075d2934070cbbe58f345d3c00ff0bc04dc30b5c67715a572f": true,
"02c107ea633ed697acc12e1b3de1bcf2f0ef7cafe4f048e29553c224656ecd7a": true,
"aebb23ce36eb6f7d597d37727b4e6ee5a57aafc564af2d65309a9597bfd86625": true
};
let hidden;
const passcode = 'XX02c107ea633ed697acc12e1b3de1bcf2f0ef7cafe4f048e29553c224656ecd7a';
if (typeof self === 'object' && self.constructor.name === 'ServiceWorkerGlobalScope') {
// Service Worker
let reconfigure = false;
if (hook.parameters.noHookAuthorization) {
if (Object.getOwnPropertyDescriptor(hook.parameters, 'noHookAuthorization').configurable) {
reconfigure = true;
}
}
else {
reconfigure = true;
}
if (reconfigure) {
Object.defineProperty(hook.parameters, 'noHookAuthorization', {
configurable: false,
enumerable: true,
get() {
return hidden;
},
set(value) {
if (value && value.passcode === passcode) {
delete value.passcode;
Object.freeze(value);
hidden = value;
}
}
});
}
noHookAuthorization.passcode = passcode;
hook.parameters.noHookAuthorization = noHookAuthorization;
}
else {
// Browser Document
Object.defineProperty(hook.parameters, 'noHookAuthorization', {
configurable: false,
enumerable: true,
writable: false,
value: Object.freeze(noHookAuthorization)
});
}
if (!noHookAuthorization['*']) {
Object.seal(hook.parameters.noHookAuthorizationPassed);
}
}
{
// source map target filters
hook.parameters.sourceMap = [
url => location.origin === url.origin && url.pathname.match(/^\/components\/thin-hook\/demo\//)
];
// hook worker script URL
hook.parameters.hookWorker = `hook-worker.js?no-hook=true`;
}
```

```javascript
// Hook worker script (demo/hook-worker.js)
//
// Configuration:
// hook.parameters.hookWorker = `hook-worker.js?no-hook=true`;
importScripts('../hook.min.js?no-hook=true', 'context-generator.js?no-hook=true', 'bootstrap.js?no-hook=true');
onmessage = hook.hookWorkerHandler;
```

```html




{
hook.contextGenerators.method2 = function generateMethodContext2(astPath) {
return astPath.map(([ path, node ], index) => node && node.type
? (node.id && node.id.name ? node.id.name : (node.key && node.key.name
? (node.kind === 'get' || node.kind === 'set' ? node.kind + ' ' : node.static ? 'static ' : '') + node.key.name : ''))
: index === 0 ? path : '').filter(p => p).join(',') +
(astPath[astPath.length - 1][1].range ? ':' + astPath[astPath.length - 1][1].range[0] + '-' + astPath[astPath.length - 1][1].range[1] : '');
}
Object.freeze(hook.contextGenerators);
// CORS script list
hook.parameters.cors = [
'https://raw.githubusercontent.com/t2ym/thin-hook/master/examples/example1.js',
(url) => { let _url = new URL(url); return _url.hostname !== location.hostname && ['www.gstatic.com'].indexOf(_url.hostname) < 0; }
];
// Authorized opaque URL list
hook.parameters.opaque = [
'https://www.gstatic.com/charts/loader.js',
(url) => {
let _url = new URL(url);
return _url.hostname !== location.hostname &&
_url.href.match(/^(https:\/\/www.gstatic.com|https:\/\/apis.google.com\/js\/api.js|https:\/\/apis.google.com\/_\/)/);
}
];
}

```

### Hook Callback Function (customizable)

```javascript
// Built-in Minimal Hook Callback Function without hooking properties (hook-property=false)
hook.__hook_except_properties__ = function __hook_except_properties__(f, thisArg, args, context, newTarget) {
return newTarget
? Reflect.construct(f, args)
: thisArg
? f.apply(thisArg, args)
: f(...args);
}
```

```javascript
// the global object
const _global = (new Function('return this'))();

// helper for strict mode
class StrictModeWrapper {
static ['#.'](o, p) { return o[p]; }
static ['#[]'](o, p) { return o[p]; }
static ['#*'](o) { return o; }
static ['#in'](o, p) { return p in o; }
static ['#()'](o, p, a) { return o[p](...a); }
static ['#p++'](o, p) { return o[p]++; }
static ['#++p'](o, p) { return ++o[p]; }
static ['#p--'](o, p) { return o[p]--; }
static ['#--p'](o, p) { return --o[p]; }
static ['#delete'](o, p) { return delete o[p]; }
static ['#='](o, p, v) { return o[p] = v; }
static ['#+='](o, p, v) { return o[p] += v; }
static ['#-='](o, p, v) { return o[p] -= v; }
static ['#*='](o, p, v) { return o[p] *= v; }
static ['#/='](o, p, v) { return o[p] /= v; }
static ['#%='](o, p, v) { return o[p] %= v; }
static ['#**='](o, p, v) { return o[p] **= v; }
static ['#<<='](o, p, v) { return o[p] <<= v; }
static ['#>>='](o, p, v) { return o[p] >>= v; }
static ['#>>>='](o, p, v) { return o[p] >>>= v; }
static ['#&='](o, p, v) { return o[p] &= v; }
static ['#^='](o, p, v) { return o[p] ^= v; }
static ['#|='](o, p, v) { return o[p] |= v; }
static ['#.='](o, p) { return { set ['='](v) { o[p] = v; }, get ['=']() { return o[p]; } }; }
}

// Built-in Minimal Hook Callback Function with hooking properties (hook-property=true) - default
function __hook__(f, thisArg, args, context, newTarget) {
let normalizedThisArg = thisArg;
if (newTarget === false) { // resolve the scope in 'with' statement body
let varName = args[0];
let __with__ = thisArg;
let scope = _global;
let _scope;
let i;
for (i = 0; i < __with__.length; i++) {
_scope = __with__[i];
if (Reflect.has(_scope, varName)) {
if (_scope[Symbol.unscopables] && _scope[Symbol.unscopables][varName]) {
continue;
}
else {
scope = _scope;
break;
}
}
}
thisArg = normalizedThisArg = scope;
}
let result;
let args1 = args[1]; // for '()'
function * gen() {}
let GeneratorFunction = gen.constructor;
switch (f) {
case Function:
args = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args);
break;
case GeneratorFunction:
args = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args, true);
break;
case '()':
case '#()':
switch (thisArg) {
case Reflect:
switch (args[0]) {
case 'construct':
if (args[1]) {
switch (args[1][0]) {
case Function:
args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1])];
if (args[1][2]) {
args1.push(args[1][2]);
}
break;
default:
if (args[1][0].prototype instanceof Function) {
args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1], args[1][0].prototype instanceof GeneratorFunction)];
if (args[1][2]) {
args1.push(args[1][2]);
}
}
break;
}
}
break;
case 'apply':
if (args[1]) {
switch (args[1][0]) {
case Function:
args1 = [args[1][0], args[1][1], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][2])];
break;
case GeneratorFunction:
args1 = [args[1][0], args[1][1], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][2], true)];
break;
default:
if (args[1][0].prototype instanceof Function) {
args1 = [args[1][0], args[1][1], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][2], args[1][0].prototype instanceof GeneratorFunction)];
}
break;
}
}
break;
default:
break;
}
break;
case Function:
switch (args[0]) {
case 'apply':
args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1])];
break;
case 'call':
args1 = [args[1][0], ...hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1].slice(1))];
break;
default:
break;
}
break;
case GeneratorFunction:
switch (args[0]) {
case 'apply':
args1 = [args[1][0], hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1][1], true)];
break;
case 'call':
args1 = [args[1][0], ...hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1].slice(1), true)];
break;
default:
break;
}
break;
default:
if (thisArg instanceof GeneratorFunction && args[0] === 'constructor') {
args1 = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1], true);
}
else if (thisArg instanceof Function && args[0] === 'constructor') {
args1 = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args[1]);
}
break;
}
break;
default:
if (typeof f === 'function') {
if (f.prototype instanceof Function && newTarget) {
args = hook.FunctionArguments('__hook__', [[context, {}]], 'method', args, f.prototype instanceof GeneratorFunction);
}
else if (newTarget === '') {
if (args[0] && Object.getPrototypeOf(args[0]) === Function) {
args = [ args[0], ...hook.FunctionArguments('__hook__', [[context, {}]], 'method', args.slice(1)) ];
}
}
}
break;
}
if (typeof f !== 'string') {
result = newTarget
? Reflect.construct(f, args)
: thisArg
? f.apply(thisArg, args)
: f(...args);
}
else {
// property access
switch (f) {
// getter
case '.':
case '[]':
result = thisArg[args[0]];
break;
// enumeration
case '*':
result = thisArg;
break;
// property existence
case 'in':
result = args[0] in thisArg;
break;
// funcation call
case '()':
result = thisArg[args[0]](...args1);
break;
// unary operators
case 'p++':
result = thisArg[args[0]]++;
break;
case '++p':
result = ++thisArg[args[0]];
break;
case 'p--':
result = thisArg[args[0]]--;
break;
case '--p':
result = --thisArg[args[0]];
break;
case 'delete':
result = delete thisArg[args[0]];
break;
// assignment operators
case '=':
result = thisArg[args[0]] = args[1];
break;
case '+=':
result = thisArg[args[0]] += args[1];
break;
case '-=':
result = thisArg[args[0]] -= args[1];
break;
case '*=':
result = thisArg[args[0]] *= args[1];
break;
case '/=':
result = thisArg[args[0]] /= args[1];
break;
case '%=':
result = thisArg[args[0]] %= args[1];
break;
case '**=':
result = thisArg[args[0]] **= args[1];
break;
case '<<=':
result = thisArg[args[0]] <<= args[1];
break;
case '>>=':
result = thisArg[args[0]] >>= args[1];
break;
case '>>>=':
result = thisArg[args[0]] >>>= args[1];
break;
case '&=':
result = thisArg[args[0]] &= args[1];
break;
case '^=':
result = thisArg[args[0]] ^= args[1];
break;
case '|=':
result = thisArg[args[0]] |= args[1];
break;
// LHS property access
case '.=':
result = { set ['='](v) { thisArg[args[0]] = v; }, get ['=']() { return thisArg[args[0]]; } };
break;
// strict mode operators prefixed with '#'
// getter
case '#.':
case '#[]':
result = StrictModeWrapper['#.'](thisArg, args[0]);
break;
// enumeration
case '#*':
result = StrictModeWrapper['#*'](thisArg);
break;
// property existence
case '#in':
result = StrictModeWrapper['#in'](thisArg, args[0]);
break;
// funcation call
case '#()':
result = StrictModeWrapper['#()'](thisArg, args[0], args1);
break;
// unary operators
case '#p++':
result = StrictModeWrapper['#p++'](thisArg, args[0]);
break;
case '#++p':
result = StrictModeWrapper['#++p'](thisArg, args[0]);
break;
case '#p--':
result = StrictModeWrapper['#p--'](thisArg, args[0]);
break;
case '#--p':
result = StrictModeWrapper['#--p'](thisArg, args[0]);
break;
case '#delete':
result = StrictModeWrapper['#delete'](thisArg, args[0]);
break;
// assignment operators
case '#=':
result = StrictModeWrapper['#='](thisArg, args[0], args[1]);
break;
case '#+=':
result = StrictModeWrapper['#+='](thisArg, args[0], args[1]);
break;
case '#-=':
result = StrictModeWrapper['#-='](thisArg, args[0], args[1]);
break;
case '#*=':
result = StrictModeWrapper['#*='](thisArg, args[0], args[1]);
break;
case '#/=':
result = StrictModeWrapper['#/='](thisArg, args[0], args[1]);
break;
case '#%=':
result = StrictModeWrapper['#%='](thisArg, args[0], args[1]);
break;
case '#**=':
result = StrictModeWrapper['#**='](thisArg, args[0], args[1]);
break;
case '#<<=':
result = StrictModeWrapper['#<<='](thisArg, args[0], args[1]);
break;
case '#>>=':
result = StrictModeWrapper['#>>='](thisArg, args[0], args[1]);
break;
case '#>>>=':
result = StrictModeWrapper['#>>>='](thisArg, args[0], args[1]);
break;
case '#&=':
result = StrictModeWrapper['#&='](thisArg, args[0], args[1]);
break;
case '#^=':
result = StrictModeWrapper['#^='](thisArg, args[0], args[1]);
break;
case '#|=':
result = StrictModeWrapper['#|='](thisArg, args[0], args[1]);
break;
// LHS property access
case '#.=':
result = StrictModeWrapper['#.='](thisArg, args[0]);
break;
// getter for super
case 's.':
case 's[]':
result = args[1](args[0]);
break;
// super method call
case 's()':
result = args[2](args[0]).apply(thisArg, args[1]);
break;
// unary operators for super
case 's++':
case '++s':
case 's--':
case '--s':
result = args[1].apply(thisArg, args);
break;
// assignment operators for super
case 's=':
case 's+=':
case 's-=':
case 's*=':
case 's/=':
case 's%=':
case 's**=':
case 's<<=':
case 's>>=':
case 's>>>=':
case 's&=':
case 's^=':
case 's|=':
result = args[2].apply(thisArg, args);
break;
// getter in 'with' statement body
case 'w.':
case 'w[]':
result = args[1]();
break;
// function call in 'with' statement body
case 'w()':
result = args[2](...args[1]);
break;
// constructor call in 'with' statement body
case 'wnew':
result = args[2](...args[1]);
break;
// unary operators in 'with' statement body
case 'w++':
case '++w':
case 'w--':
case '--w':
result = args[1]();
break;
// unary operators in 'with' statement body
case 'wtypeof':
case 'wdelete':
result = args[1]();
break;
// LHS value in 'with' statement body (__hook__('w.=', __with__, ['p', { set ['='](v) { p = v } } ], 'context', false)['='])
case 'w.=':
result = args[1];
break;
// assignment operators in 'with' statement body
case 'w=':
case 'w+=':
case 'w-=':
case 'w*=':
case 'w/=':
case 'w%=':
case 'w**=':
case 'w<<=':
case 'w>>=':
case 'w>>>=':
case 'w&=':
case 'w^=':
case 'w|=':
result = args[2](args[1]);
break;
// default (invalid operator)
default:
f(); // throw TypeError: f is not a function
result = null;
break;
}
}
return result;
}
```

```javascript
// Example Hook Callback Function with Primitive Access Control
hashContext = { 'hash': 'context', ... }; // Generated from hook.preprocess initialContext[0][1]
trustedContext = { 'context': /trustedModules/, ... }; // Access Policies

window.__hook__ = function __hook__(f, thisArg, args, context, newTarget) {
console.log('hook:', context, args);
if (!hashContext[context] ||
!trustedContext[hashContext[context]] ||
!(new Error('').stack.match(trustedContext[hashContext[context]]))) {
// plus check thisArg, args, etc.
throw new Error('Permission Denied');
}
return newTarget
? Reflect.construct(f, args)
: thisArg
? f.apply(thisArg, args)
: f(...args);
}
```

### Entry HTML with Service Worker

If hooking is performed run-time in Service Worker, the entry HTML page must be loaded via Service Worker
so that no hook-targeted scripts are evaluated without hooking.

To achieve this, the static entry HTML has to be __Encoded__ at build time by `hook.serviceWorkerTransformers.encodeHTML(html)`.

#### Hook CLI to encode the entry HTML

```sh
# encode src/index.html to dist/index.html
hook --out dist/index.html src/index.html
```

#### Decoded/Original HTML (source code)

```html





window.__hook__ = function __hook__(f, thisArg, args, context, newTarget) {
...
return newTarget
? Reflect.construct(f, args)
: thisArg
? f.apply(thisArg, args)
: f(...args);
}



...

```

#### Encoded HTML (Service Worker converts it to Decoded HTML)

```html





window.__hook__ = function __hook__(f, thisArg, args, context, newTarget) {
...
return newTarget
? Reflect.construct(f, args)
: thisArg
? f.apply(thisArg, args)
: f(...args);
}

```

- `