원형 오염에 대한 조언과primordials.js

개시하다


이 글은 원형 오염에 대한 건의와 전 세계와 원형 속성의 변경을 둘러싼 코드다.제이스를 소개합니다.내일 못 쓸 말이 많을 것 같으니 편하게 읽어주세요.

원형 오염


JavaScript는 원형에 기반한 언어입니다.대상마다 원형([[Prototype]] 내부 슬롯)이 있고 추적을 통해 계승(원형 체인)을 나타낸다.
https://developer.mozilla.org/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
JavaScript는 기본적으로 동결된 객체가 없으며 원하는 대로 속성을 추가할 수 있습니다.이전Prototype JavaScript FrameworkMooTools의 프로그램 라이브러리는 광범위하게 사용되었고 내부 대상의 원형에 독자적인 방법을 추가하여 웹 페이지를 개발했다.그러나 대상이 직접 가지고 있는 속성과 원형 체인에서 가지고 있는 속성을 구분하기 어려운 등 이유로 오류가 발생했다.
// Object.prototype を汚染する
Object.prototype.foo = 1;

const obj = { bar: 2 };

// obj のプロトタイプチェーンの中に Object.prototype が存在する
console.log(Object.prototype.isPrototypeOf(obj)); // true
console.log(obj instanceof Object); // => true

// 値を取得する際にプロトタイプを辿る
console.log(obj.foo); // => 1
console.log(obj.bar); // => 2

// in 演算子ではオブジェクトが直接所有するプロパティかどうか判定できない
console.log("foo" in obj); // => true
console.log("bar" in obj); // => true

// Object#hasOwnProperty を使って判定できる
const hasOwnProperty = Object.prototype.hasOwnProperty;
console.log(hasOwnProperty.call(obj, "foo")); // => false
console.log(hasOwnProperty.call(obj, "bar")); // => true

// 最近は ES2022 Object.hasOwn を使うことができる
console.log(Object.hasOwn(obj, "foo")); // => false
console.log(Object.hasOwn(obj, "bar")); // => true
이런 이유로 원형을 함부로 만지는 것을 원형오염(Protoype Pollection)[1]이라고 한다.
현재 주류는 낡은 실행 환경에서만 새로운 규격의 추가를 처리할 수 있는 방법polyfill이기 때문에 프로그램 라이브러리 사용으로 인한 원형 오염[2]을 거의 듣지 못한다.
최근 전반적으로 원형 오염이라는 단어는 원형을 악용하는 공격에 쓰인다.Node.js가 실행하는 서버에 __proto__constructor 등의 속성을 가진 JSON을 보내고, 덮어쓰기Object.prototype를 시도하는 등 예상치 못한 속성을 가진 대상으로 처리하는 공격으로 유명하다.__proto__ 자세한 내용은 과거 기사를 보십시오.
https://zenn.dev/petamoriken/articles/a211183011cd58

전역과 원형 변경을 견딜 수 있는 코드


갑작스럽지만 다음 모듈이 있다고 가정해 보세요.이 전선은 안전합니까?
const rainbowColors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];

export function isRainbowColor(color) {
  return rainbowColors.includes(String(color).toLowerCase());
}
이 모듈이 처음 실행될 때 문제가 없더라도 실행isRainbowColor할 때 전역 변수String와 원형 방법Array#includes, String#toLowerCase이 다를 수 있다.처음 모듈을 실행할 때 전 세계와 원형이 변경되지 않은 전제에서 다음과 같은 코드가 비교적 안전하다고 할 수 있다.
const rainbowColors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];

const NativeString = String;
const toLowerCase = Function.call.bind(String.prototype.toLowerCase);
const includes = Function.call.bind(Array.prototype.includes);

export function isRainbowColor(color) {
  return includes(rainbowColors, toLowerCase(NativeString(color));
}
자바스크립트 원형의 장점을 완전히 잃은 코드가 너무 어리석다고 생각했을 수도 있다.하지만 이렇게 안전 옆에 거꾸로 설치된 프로그램 라이브러리도 있다.polyfill 라이브러리core-jses-shims는 그것[3]에 해당한다.
응용 프로그램 개발에서 이런 코드를 쓰는 것은 비현실적이다.그러나 엄격한 실시를 최우선으로 하는 프로그램 라이브러리에서 내부 대상의 전역 변수와 원형이 바뀌었더라도 문제 없이 움직이고 있다는 것을 알려주면 된다.

Stage 1 Get Intrinsic


아까처럼 코드 작성을 돕는 제안인 겟인트라넷은 2021년 8월 TC39meeting에서 스테이지 1이 됐다.
https://github.com/ljharb/proposal-get-intrinsic
이 제안은 시종 보조적이어서 장래의 원형 변경에 대해서는 다른 방법을 취해야 한다.

primordials.js


Node.js와 Deno는 내부 코드에서 JavaScript를 사용하기 때문에 사용자에게 API를 제공합니다.그 코드에모든 전역 변수와 구축 대상의 원형 속성 변화를 감당할 수 있는 js가 있습니다.
https://github.com/nodejs/node/blob/e937662dec4adc6ac1cd65afd4cbda79703d5ecc/lib/internal/per_context/primordials.js
아까의 코드 예만 봤다면 원형 방법을 변수로 모듈의 작용 범위에 캐시하고 편지관 수를 집행할 때 그것을 사용하면 해결할 수 있었을 텐데 그렇게 간단하지는 않았다.다음 코드는 안전합니까?
const NativeSet = Set;
const forEach = Function.call.bind(Set.prototype.forEach);
const push = Function.call.bind(Array.prototype.push);

export function toUnique(array) {
  const ret = [];
  forEach(new NativeSet(array), (val) => push(ret, val));
  return ret;
}
실제로 Set의 구조기는 내부에서'dd'방법을 사용하는 규격이다.는 빌딩을 바꾸면Set#add 행동을 간단하게 바꿀 수 있다는 뜻이다.또 파라미터를 이전하기 위해 집행@@iterator하는 방법도 고려해야 한다.
primordials.js세트의 원형을 완전히 복제하는 방법을 만들고, SafeIterator로 구조기에 전송된 파라미터를 랩의 SafeSet 등급으로 만든다에서 역기로 대응한다.
이 코드를 읽어도 평소에 쓴 자바스크립트에는 아무런 영향이 없을 것 같지만 ECMAScript를 보는 방법을 좋아하는 사람들에게는 재미있을 것 같다.Array#concert는 @isConceatSpreadable을 고려해야 합니다. 등의 학습이 있습니다.

앞으로 어떻게 될까


앞으로도 전역 변수와 원형 변경을 견딜 수 있는 코드를 쓰기 위해 이렇게 규격을 완전히 파악하고 최신 코드를 강제로 쓰게 될까.또 최근 npm 패키지에 악성코드가 섞여 있어 보안 문제도 커지고 있다.대처법 없어?

Node.분류:js


사실 노드는js에는 실험 CLI 옵션--frozen-intrinsics이 있습니다.이것을 사용하면 Object.prototype 등 건축물 대상을 동결할 수 있다.
https://nodejs.org/dist/latest-v16.x/docs/api/cli.html#--frozen-intrinsics
문서에 설명된 대로 polyfill--require을 로드하려면 옵션을 사용해야 합니다(ES Modules는 사용할 수 없을 것 같습니다).
실현은 아래에 있다.
https://github.com/nodejs/node/blob/f8035ecbbd9f32ec5ae398495cd645f00c4b0c51/lib/internal/freeze_intrinsics.js

Stage 1 Secure ECMAScript


Stage 1 Secure ECMAScript 제안이곳에서도 건물 대상을 동결할 수 있다.
https://github.com/tc39/proposal-ses
아직 스테이지 1에 불과해 앞으로 어떻게 될지 모르지만 lockdown라는 글로벌 함수를 통해 --frozen-intrinsics와 같은 상황을 이룰 수 있다.
lockdown();

Object.isFrozen(Object.prototype); // => true
Object.isFrozen(Array.prototype); // => true
처음부터 동결되면polyfill을 전혀 사용할 수 없기 때문에 믿을 만한polyfill을 읽은 후에 호출하는 것이 아닐까lockdown[4]라고 생각합니다.
아래의 부팅 액세서리 모듈을 준비하여 신청서의 처음에 읽으면 다른 의존 포장이 고장나지 않을 것입니다. 저는 이렇게 하는 것이 좋다고 생각합니다.
// polyfill ライブラリを読み込む
import "core-js/stable";

// もしくは自前で polyfill を追加する
Object.defineProperty(Array.prototype, "filterReject", {
  value: function filterReject(predicate, thisArg = undefined) {
    // 実装略
  },
});

// Object.prototype に対するプロトタイプ汚染攻撃の脅威はないが
// 作ったオブジェクトの [[Prototype]] の変更を試みる攻撃の可能性はまだある
// 消しても依存パッケージが問題なく動くなら消しておく
delete Object.prototype.__proto__;

// intrinsics を凍結する
lockdown();
라이브러리 개발자 입장에서는 내부 대상을 동결하는 데 미치는 영향 범위가 커 사용할 수 없었지만, 앱 개발자 입장에서는 반가운 제안이었다.
이 제안을 한 사람들은 실험적인 프로그램 라이브러리를 공개했다.
https://github.com/endojs/endo/tree/master/packages/ses

매듭을 짓다


원형 오염에 대한 조언, 그리고primordials.js를 들어 봤어요.
안전성에 관해서는 디노와 웹애슬리브리처럼 권한을 명확히 부여해야 하는 집행 환경에 대한 말[5][6]도 있고, 언어 방법에 대한 논의도 있다.
여러분도 이 근처를 쫓아가세요.
각주
이 반동작용에서 모든 함수$가 탄생했다jQuery.↩︎
MDN에도 polyfill 이외의 확장 원형은 디자인 오류라고 기재되어 있습니다 . ↩︎
졸작float16도 그렇게 설치했어요.↩︎
polyfill을 통해 기존 내장 대상에 대한 추가 방법이 추가된 경우 문제가 없지만 Temporal처럼 언어 규격에 새로운 명명 공간을 추가한 API가 추가되면 낡은 자바스크립트 엔진이 존재를 식별할 수 없기 때문에 같은 방법으로 해결할 수 없는 문제가 있다.↩︎
Deno의 CLI 옵션에서 권한을 명시적으로 지정해야 합니다. . ↩︎
WebAssiembly에서는 모듈 단위로 처리되는 API를 명시적으로 제시해야 합니다. . ↩︎

좋은 웹페이지 즐겨찾기