Tombstone harden
DELETE
packages/harden/.prettierrc.json
-{
- "trailingComma": "all",
- "singleQuote": true
-}

MODIFY
packages/harden/NEWS.md
packages/harden/NEWS.md
User-visible changes in harden:
-## Next release
+## Release 0.2.0 (28-Sep-2020)
-* No changes yet.
+* The `harden` package has been deprecated.
+ Please use the shim for `harden` provided [SES][].
+
+ SES: https://github.com/Agoric/SES-shim/blob/master/packages/ses/README.md#harden
## Release 0.1.0 (29-Jul-2020)

MODIFY
packages/harden/README.md
packages/harden/README.md
# Harden
-[![Build Status][circleci-svg]][circleci-url]
-[![dependency status][deps-svg]][deps-url]
-[![dev dependency status][dev-deps-svg]][dev-deps-url]
-[![License][license-image]][license-url]
+The `harden` package has been deprecated.
+Please use the shim for `harden` provided [SES][].
-Build a defensible API surface around an object by freezing all reachable properties.
+SES: https://github.com/Agoric/SES-shim/blob/master/packages/ses/README.md#harden
-## How to use
+Since calling `harden` before `lockdown` provides no security guarantees, and
+since a single `harden` should be shared throughout a realm, SES now provides
+this singleton and exposes it globally.
-> Note: To fully freeze all reachable proporties, `harden()` must be run in a [SES](https://github.com/Agoric/SES) environment. This package, used by itself, is insecure and should only be used for more easily testing code that will be run in SES.
-
-## Background: Why do we need to "harden" objects?
-
-A "hardened" object is one which is safe to pass to untrusted code: it offers an API which can be invoked, but does not allow the untrusted code to modify the internals of the object or anything it depends upon.
-
-To better explain this, let's look at what happens if you don't harden your objects. For example, let's say we want to offer an increment-only counter API to some users:
-
-```js
-function makeCounterSet() {
- let counters = new Map();
- const API = {
- increment(name) {
- if (!counters.has(name)) {
- counters.set(name, 0);
- }
- const newValue = counters.get(name) + 1;
- counters.set(name, newValue);
- return newValue;
- },
- };
- return API;
-}
-
-const newAPI = makeCounterSet();
-```
-
-Now we hand off the counterSet API to two users:
-
-```js
-untrustedUser1(newAPI);
-untrustedUser2(newAPI);
-```
-
-Because we haven't hardened anything at this point, `untrustedUser1` can do a lot of damage. `untrustedUser1` can:
-
-**Break Functionality For Other Users**
-
-```js
-function untrustedUser1(newAPI) {
- delete newAPI.increment;
-}
-```
-
-That would prevent anyone from using the counter at all.
-
-**Snoop on Usage By Other Users**
-
-```js
-function untrustedUser1(newAPI) {
- const origIncrement = newAPI.increment;
- const otherNames = new Set();
- newAPI.increment = function(name) {
- otherNames.add(name);
- return origIncrement(name);
- };
-}
-```
-
-This lets one user learn the names being used by other user.
-
-## But what about Object.freeze()?
-
-`Object.freeze()` was created to prevent exactly this sort of misbehavior. Once an object is frozen, its properties cannot be changed (new ones cannot be added, and existing ones cannot be modified or removed). This prevents the most basic attacks:
-
-```js
-const newAPI = makeCounterSet();
-Object.freeze(newAPI);
-untrustedUser1(newAPI);
-untrustedUser2(newAPI);
-```
-
-However the API object might expose properties that point to other API objects, and Object.freeze() only protects its single argument. We want to traverse all exposed properties of our API object and freeze them too, recursively. We want to make sure the prototype chain is protected too, as well as any utilities that our API depends upon (like `Map`). If we don't the attacker, `untrustedUser1` can still violate the API Contract as in this example of prototype poisoning:
-
-```js
-function untrustedUser1(newAPI) {
- Map.prototype.set = () => {};
- Map.prototype.get = () => 0;
- Map.prototype.has = () => true;
-}
-```
-
-This changes the `Map` which our counter API relies upon: when it tries to update the value, the update is ignored, so the counter will stay at 1 forever.
-
-As a side-effect, it breaks `Map` for everyone in that Realm (which generally means everyone in the same process). This is pretty drastic, but you can imagine a situation where the target object was the only user of some shared utility, and the attacker could selectively modify the utility to affect some users without affecting others. For example, `Map.prototype.set` might look at the name and only ignore updates for specific ones.
-
-## The Solution: Recursive freezing with `harden()`
-
-`harden()` is a function which performs recursive freezing of an API surface, preventing all of the attacks described above:
-
-```js
-const newAPI = harden(makeCounterSet());
-untrustedUser1(newAPI);
-untrustedUser2(newAPI);
-```
-
-If `harden()` runs in a [SES](https://github.com/Agoric/SES) environment, all of the intrinsics (the built-in Javascript objects like `Map`, `Number`, `Array`, and so on) are already frozen. In a SES environment, to interact with untrusted code safely according to the API that you've constructed, you just need to `harden()` the objects that you give to other code (and any custom prototypes you might be using). Outside of SES, `harden()` is insecure and should be used for testing only.
-
-
-## MakeHardener and creating a custom harden() function
-
-The package [`@agoric/make-hardener`](https://www.npmjs.com/package/@agoric/make-hardener) provides a `makeHardener()` which can be used to build your own `harden()` function. `makeHardener` does not know about any specific intrinsics, and must be passed that information. When you call `makeHardener()`, you give it a set of stopping points, and the recursive property walk will stop its search when it runs into one of these points. The resulting `harden()` will throw an exception if anything it freezes has a prototype that is not already in the set of stopping points (or was frozen during the same call).
-
-The provided harden() function is created by calling makeHardener() on a specific set of stopping points. Thus, makeHardener is bundled (see package-lock.json for the actual version) in this package for ease of use.
-
-For everyday usage, you probably want to use the `harden()` provided in [SES](https://github.com/Agoric/SES) instead of creating your own. If you want to test your code before using it in SES, you can use this package [@agoric/harden package](https://github.com/Agoric/Harden). (Note that without SES freezing the intrinsics, `harden()` is insecure, and should be used for testing purposes only.)
-
-[circleci-svg]: https://circleci.com/gh/Agoric/harden.svg?style=svg
-[circleci-url]: https://circleci.com/gh/Agoric/harden
-[deps-svg]: https://david-dm.org/Agoric/Harden.svg
-[deps-url]: https://david-dm.org/Agoric/Harden
-[dev-deps-svg]: https://david-dm.org/Agoric/Harden/dev-status.svg
-[dev-deps-url]: https://david-dm.org/Agoric/Harden?type=dev
-[license-image]: https://img.shields.io/badge/License-Apache%202.0-blue.svg
-[license-url]: LICENSE
+The last coherent version of `harden`, prior to deprecation, was version
+`0.0.8`.

DELETE
packages/harden/index.d.ts
-// Type definitions for @agoric/harden 0.0.4
-// Project: Agoric, Inc.
-// Definitions by: Michael FIG <michael+jessica@fig.org>
-
-export = harden;
-
-// FIXME: Would like to return harden.Hardened<T>, but that doesn't yet behave properly.
-declare function harden<T>(root: T): T;
-
-declare namespace harden {
- type Primitive = undefined | null | boolean | string | number;
-
- export type Hardened<T> =
- T extends Function ? HardenedFunction<T> :
- T extends Primitive ? Readonly<T> :
- T extends Array<infer U> ? HardenedArray<U> :
- // All others are manually hardened.
- HardenedObject<T>;
-
- type HardenedFunction<T> = T; // FIXME: Escape hatch.
- interface HardenedArray<T> extends Readonly<Array<Hardened<T>>> {}
- type HardenedObject<T> = {
- readonly [K in keyof T]: Hardened<T[K]>
- };
-}

MODIFY
packages/harden/package.json
packages/harden/package.json
{
"name": "@agoric/harden",
- "version": "0.1.0+1-dev",
- "description": "Create a 'hardener' which freezes the API surface of a set of objects",
+ "version": "0.2.0",
+ "description": "Please use the package named 'ses' directly to shim 'harden'",
"author": "Agoric",
"license": "Apache-2.0",
"keywords": [
"Object.freeze",
"freeze"
],
- "homepage": "https://github.com/Agoric/harden#readme",
+ "homepage": "https://github.com/Agoric/SES-shim/blob/master/packages/ses/README.md#harden",
"bugs": {
- "url": "https://github.com/Agoric/harden/issues"
+ "url": "https://github.com/Agoric/SES-shim/issues"
},
"repository": {
"type": "git",
- "url": "git+https://github.com/Agoric/ses-shim.git"
- },
- "directories": {
- "test": "test"
- },
- "files": [
- "src",
- "dist",
- "index.d.ts",
- "LICENSE*"
- ],
- "type": "module",
- "main": "./dist/harden.cjs",
- "browser": "./dist/harden.umd.js",
- "unpkg": "./dist/harden.umd.js",
- "module": "./src/main.js",
- "types": "index.d.ts",
- "exports": {
- "./package.json": "./package.json",
- ".": {
- "import": "./src/main.js",
- "require": "./dist/harden.cjs",
- "browser": "./dist/harden.umd.js"
- }
- },
- "scripts": {
- "prepublish": "yarn clean && yarn build",
- "clean": "rm -rf dist",
- "depcheck": "depcheck",
- "lint": "eslint '**/*.js'",
- "lint-fix": "eslint --fix '**/*.js'",
- "test": "tap --no-esm --no-coverage --reporter spec 'test/**/*.test.js'",
- "build": "rollup --config rollup.config.js"
- },
- "dependencies": {
- "@agoric/make-hardener": "^0.1.0"
- },
- "devDependencies": {
- "@rollup/plugin-node-resolve": "^6.1.0",
- "babel-eslint": "^10.0.3",
- "eslint": "^6.8.0",
- "eslint-config-airbnb-base": "^14.0.0",
- "eslint-config-prettier": "^6.9.0",
- "eslint-plugin-eslint-comments": "^3.1.2",
- "eslint-plugin-import": "^2.19.1",
- "eslint-plugin-prettier": "^3.1.2",
- "prettier": "^1.19.1",
- "rollup": "1.31.0",
- "rollup-plugin-node-resolve": "5.2.0",
- "rollup-plugin-terser": "^5.1.3",
- "tap": "14.10.5"
+ "url": "git+https://github.com/Agoric/SES-shim.git"
}
}

DELETE
packages/harden/rollup.config.js
-import resolve from '@rollup/plugin-node-resolve';
-import { terser } from 'rollup-plugin-terser';
-import fs from 'fs';
-
-const metaPath = new URL('package.json', import.meta.url).pathname;
-const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
-const name = meta.name.split('/').pop();
-const umd = meta.umd || name;
-
-export default [
- {
- input: 'src/main.js',
- output: [
- {
- file: `dist/${name}.mjs`,
- format: 'esm',
- },
- {
- file: `dist/${name}.cjs`,
- format: 'cjs',
- },
- ],
- plugins: [resolve()],
- },
- {
- input: 'src/main.js',
- output: {
- file: `dist/${name}.umd.js`,
- format: 'umd',
- name: umd,
- },
- plugins: [resolve()],
- },
- {
- input: 'src/main.js',
- output: {
- file: `dist/${name}.umd.min.js`,
- format: 'umd',
- name: umd,
- },
- plugins: [resolve(), terser()],
- },
-];

DELETE
packages/harden/src/main.js
-/* global SES */
-// Adapted from SES/Caja - Copyright (C) 2011 Google Inc.
-// Copyright (C) 2018 Agoric
-
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import makeHardener from '@agoric/make-hardener';
-
-// Try to use SES's own harden if available.
-let h = typeof harden === 'undefined' ? undefined : harden;
-if (h === undefined) {
- // Legacy SES compatibility.
- h = typeof SES === 'undefined' ? undefined : SES.harden;
-}
-
-if (h === undefined) {
- // Warn if they haven't explicitly set harden or SES.harden.
- console.warn(
- `SecurityWarning: '@agoric/harden' doesn't prevent prototype poisoning without SES`,
- );
-}
-
-// Create the shim if h is anything falsey.
-if (!h) {
- h = makeHardener();
-}
-
-const constHarden = h;
-export default constHarden;

DELETE
packages/harden/test/harden.test.js
-import tap from 'tap';
-import harden from '../src/main.js';
-
-const { test } = tap;
-
-// `harden` is only intended to work after `lockdown`. However,
-// to test it standalone, we need to freeze at least these ahead
-// of time.
-harden([Array.prototype, Object.prototype, Function.prototype]);
-
-test('harden', t => {
- const o = { a: {} };
- t.equal(harden(o), o);
- t.ok(Object.isFrozen(o));
- t.ok(Object.isFrozen(o.a));
- t.end();
-});
-
-test('complain about prototype not in roots', t => {
- const parent = { I_AM_YOUR: 'parent' };
- const o = { a: {} };
- Object.setPrototypeOf(o, parent);
- t.throws(() => harden(o), TypeError);
- // if harden() throws TypeError, we make no claims about what properties
- // got frozen. However, we know for sure that the prototype shouldn't be
- // frozen: this is what saves us from the "ice-9" freeze-the-world scenario
- t.notOk(Object.isFrozen(parent));
- t.end();
-});
-
-test('harden the same thing twice', t => {
- const o = { a: {} };
- t.equal(harden(o), o);
- t.equal(harden(o), o);
- t.ok(Object.isFrozen(o));
- t.ok(Object.isFrozen(o.a));
- t.end();
-});
-
-test('harden objects with cycles', t => {
- const o = { a: {} };
- o.a.foo = o;
- t.equal(harden(o), o);
- t.ok(Object.isFrozen(o));
- t.ok(Object.isFrozen(o.a));
- t.end();
-});
-
-test('harden overlapping objects', t => {
- const o1 = { a: {} };
- const o2 = { a: o1.a };
- t.equal(harden(o1), o1);
- t.ok(Object.isFrozen(o1));
- t.ok(Object.isFrozen(o1.a));
- t.notOk(Object.isFrozen(o2));
- t.equal(harden(o2), o2);
- t.ok(Object.isFrozen(o2));
- t.end();
-});
-
-test('harden function', t => {
- const o = _a => 1;
- t.equal(harden(o), o);
- t.ok(Object.isFrozen(o));
- t.end();
-});

Tombstone harden