Skip to content

Commit

Permalink
fix datex injection vulnerability for datex.get
Browse files Browse the repository at this point in the history
  • Loading branch information
benStre committed Nov 17, 2023
1 parent 586d5d7 commit f1e4f88
Show file tree
Hide file tree
Showing 10 changed files with 56 additions and 264 deletions.
15 changes: 10 additions & 5 deletions datex_short.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { baseURL, Runtime, PrecompiledDXB, Type, Pointer, Ref, PointerProperty, primitive, any_class, Target, IdEndpoint, TransformFunctionInputs, AsyncTransformFunction, TransformFunction, TextRef, Markdown, DecimalRef, BooleanRef, IntegerRef, MinimalJSRef, RefOrValue, PartialRefOrValueObject, datex_meta, ObjectWithDatexValues, Compiler, endpoint_by_endpoint_name, endpoint_name, Storage, compiler_scope, datex_scope, DatexResponse, target_clause, ValueError, logger, Class, getUnknownMeta, Endpoint, INSERT_MARK, CollapsedValueAdvanced, CollapsedValue, SmartTransformFunction, compiler_options, activePlugins, METADATA, handleDecoratorArgs, RefOrValueObject, PointerPropertyParent, InferredPointerProperty, RefLike } from "./datex_all.ts";

/** make decorators global */
import { validate as _validate, property as _property, sync as _sync, endpoint as _endpoint, template as _template, jsdoc as _jsdoc} from "./datex_all.ts";
import { assert as _assert, property as _property, sync as _sync, endpoint as _endpoint, template as _template, jsdoc as _jsdoc} from "./datex_all.ts";
import { effect as _effect, always as _always, toggle as _toggle, map as _map, equals as _equals, selectProperty as _selectProperty, not as _not } from "./functions.ts";
export * from "./functions.ts";
import { NOT_EXISTING, DX_SLOTS, SLOT_GET, SLOT_SET } from "./runtime/constants.ts";
Expand All @@ -17,7 +17,7 @@ export {instance} from "./js_adapter/js_class_adapter.ts";

declare global {
const property: typeof _property;
const validate: typeof _validate;
const assert: typeof _assert;

const jsdoc: typeof _jsdoc;
const sync: typeof _sync;
Expand All @@ -40,7 +40,7 @@ declare global {
// @ts-ignore global
globalThis.property = _property;
// @ts-ignore global
globalThis.validate = _validate;
globalThis.assert = _assert;

// @ts-ignore global
globalThis.sync = _sync;
Expand Down Expand Up @@ -71,8 +71,13 @@ export async function get<T=unknown>(dx:string|URL|Endpoint, assert_type?:Type<T
Runtime.deleteURLCache(dx.toString())
}

// handle edge case blob:http:// -> escape
if (dx.toString().startsWith('blob:http://') || dx.toString().startsWith('blob:https://')) dx = `url '${dx}'`;
// escape relative paths
if (typeof dx == "string" && (dx.startsWith('./') || dx.startsWith('../'))) {
dx = new URL(dx, context_location).toString();
}

// escape urls
if (dx.toString().startsWith('http://') || dx.toString().startsWith('https://') || dx.toString().startsWith('file://') || dx.toString().startsWith('blob:http://') || dx.toString().startsWith('blob:https://')) dx = `url '${dx}'`;

const res = <T> await _datex('get (' + dx + ' )', undefined, undefined, undefined, undefined, context_location, plugins);
if (plugins) activePlugins.splice(0, activePlugins.length);
Expand Down
230 changes: 0 additions & 230 deletions deno.lock

This file was deleted.

8 changes: 4 additions & 4 deletions js_adapter/js_class_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,11 +325,11 @@ export class Decorators {

}

/** @validate: add type assertion function */
static validate(value:any, name:context_name, kind:context_kind, is_static:boolean, is_private:boolean, setMetadata:context_meta_setter, getMetadata:context_meta_getter, params:[((value:any)=>boolean)?] = []) {
if (kind != "field" && kind != "getter" && kind != "setter" && kind != "method") logger.error("Invalid use of @validate decorator");
/** @assert: add type assertion function */
static assert(value:any, name:context_name, kind:context_kind, is_static:boolean, is_private:boolean, setMetadata:context_meta_setter, getMetadata:context_meta_getter, params:[((value:any)=>boolean)?] = []) {
if (kind != "field" && kind != "getter" && kind != "setter" && kind != "method") logger.error("Invalid use of @assert decorator");
else {
if (typeof params[0] !== "function") logger.error("Invalid @validate decorator value, must be a function");
if (typeof params[0] !== "function") logger.error("Invalid @assert decorator value, must be a function");
else {
const assertionType = new Conjunction(Assertion.get(undefined, params[0], false));
setMetadata(Decorators.FORCE_TYPE, assertionType)
Expand Down
6 changes: 3 additions & 3 deletions js_adapter/legacy_decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,9 @@ export function type(...args:any[]): any {
return handleDecoratorArgs(args, Decorators.type);
}

export function validate(assertion:(val:any)=>boolean):any
export function validate(...args:any[]): any {
return handleDecoratorArgs(args, Decorators.validate, true);
export function assert(assertion:(val:any)=>boolean|string|undefined|null):any
export function assert(...args:any[]): any {
return handleDecoratorArgs(args, Decorators.assert, true);
}


Expand Down
6 changes: 6 additions & 0 deletions network/communication-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Base class for all DATEX communication interfaces
*/
export abstract class CommunicationInterface {
//TODO:
}
8 changes: 2 additions & 6 deletions runtime/pointers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3051,9 +3051,7 @@ export class Pointer<T = any> extends Ref<T> {
if (typeof value == "number" && this.type.getAllowedPropertyType(key).root_type == Type.std.integer) value = BigInt(value);

// invalid type for value?
if (!this.type.isPropertyValueAllowed(key, value)) {
throw new ValueError("Property '" + key + "' must be of type " + this.type.getAllowedPropertyType(key));
}
this.type.assertPropertyValueAllowed(key, value)

this.initShadowObjectProperty(key, value);

Expand Down Expand Up @@ -3082,9 +3080,7 @@ export class Pointer<T = any> extends Ref<T> {
if (typeof value == "number" && this.type.getAllowedPropertyType(key).root_type == Type.std.integer) value = BigInt(value);

// invalid type for value?
if (!this.type.isPropertyValueAllowed(key, value)) {
throw new ValueError("Property '" + key + "' must be of type " + this.type.getAllowedPropertyType(key));
}
this.type.assertPropertyValueAllowed(key, value)

// get current value
const current_value = this.getProperty(key);
Expand Down
4 changes: 2 additions & 2 deletions runtime/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3516,8 +3516,8 @@ export class Runtime {
const type = Type.ofValue(parent);

if (type.template && !type.isPropertyAllowed(key)) throw new ValueError("Property '" + key + "' does not exist");
if (type.template && !type.isPropertyValueAllowed(key, value)) throw new ValueError("Property '" + key + "' must be of type " + type.getAllowedPropertyType(key));

if (type.template) type.assertPropertyValueAllowed(key, value)
// check sealed tuple
if (parent instanceof Tuple && !parent.has(key)) throw new ValueError("Property '"+key.toString()+"' does not exist in <Tuple>", SCOPE)

Expand Down
5 changes: 3 additions & 2 deletions types/assertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class Assertion<T=any> extends ExtensibleFunction implements ValueConsume
}


assert<B extends boolean = false>(value:T|Tuple<T>, SCOPE?:datex_scope, return_boolean:B = false): B extends true ? boolean|Promise<boolean> : void|Promise<void> {
assert(value:T|Tuple<T>, SCOPE?:datex_scope, return_boolean = false): boolean|Promise<boolean> {
// ntarget
if (this.ntarget) {
if (this.ntarget_async) return this.checkResultPromise(<Promise<string | boolean>>this.ntarget(...(value instanceof Tuple ? value.toArray() : (value instanceof Array ? value : [value]))), return_boolean)
Expand All @@ -53,14 +53,15 @@ export class Assertion<T=any> extends ExtensibleFunction implements ValueConsume
return this.checkResult(await valid_promise, return_boolean);
}

private checkResult(valid:string|boolean|undefined, return_boolean = false) {
private checkResult(valid:string|boolean|undefined, return_boolean = false):boolean {
if (return_boolean) return valid === true || valid === VOID

if (valid !== true && valid !== VOID) {
if (valid == false) throw new AssertionError(this.scope?.decompiled ? `${this.scope.decompiled.replace(/;$/,'')} is false` : 'Invalid');
else if (typeof valid == "string") throw new AssertionError(valid);
else throw new ValueError("Invalid assertion result - must be of type <float>, <boolean>, or <text>")
}
return true;
}

handleApply(value: T|Tuple<T>, SCOPE?: datex_scope) {
Expand Down
20 changes: 10 additions & 10 deletions types/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class Logical<T> extends Set<T> {

// value clause matches against other clause
// if no atomic_class is provided, the first value clause literal is used to determine a atomic class
public static matches<T>(value:clause<T>, against:clause<T>, atomic_class?:Class<T>&LogicalComparator<T>, assertionValue = value): boolean {
public static matches<T>(value:clause<T>, against:clause<T>, atomic_class?:Class<T>&LogicalComparator<T>, assertionValue = value, throwInvalidAssertion = false): boolean {

// TODO: empty - does not match?
if (against === undefined) return false;
Expand All @@ -84,7 +84,7 @@ export class Logical<T> extends Set<T> {
if (value.size == 0) return true;

for (const p of value) {
if (!(this.matches(p, against, atomic_class))) return false;
if (!(this.matches(p, against, atomic_class, assertionValue, throwInvalidAssertion))) return false;
}
return true;
}
Expand All @@ -94,13 +94,13 @@ export class Logical<T> extends Set<T> {
if (value.size == 0) return true;

for (const p of value) {
if (this.matches(p, against, atomic_class)) return true;
if (this.matches(p, against, atomic_class, assertionValue, throwInvalidAssertion)) return true;
}
return false;
}
// not
if (value instanceof Negation) {
return !this.matches(value.not(), against, atomic_class)
return !this.matches(value.not(), against, atomic_class, assertionValue, throwInvalidAssertion)
}

// assertion
Expand All @@ -111,11 +111,11 @@ export class Logical<T> extends Set<T> {
}

// default
return this.matchesSingle(<T>Ref.collapseValue(value, true, true), against, atomic_class, assertionValue);
return this.matchesSingle(<T>Ref.collapseValue(value, true, true), against, atomic_class, assertionValue, throwInvalidAssertion);

}

private static matchesSingle<T>(atomic_value:T, against: clause<T>, atomic_class:Class<T>&LogicalComparator<T>, assertionValue = atomic_value): boolean {
private static matchesSingle<T>(atomic_value:T, against: clause<T>, atomic_class:Class<T>&LogicalComparator<T>, assertionValue = atomic_value, throwInvalidAssertion = false): boolean {

atomic_value = <T> Datex.Ref.collapseValue(atomic_value, true, true);
against = <clause<T>> Datex.Ref.collapseValue(against, true, true);
Expand All @@ -128,25 +128,25 @@ export class Logical<T> extends Set<T> {
// TODO:empty disjunction == any?
if (against.size == 0) return true;
for (const t of against) {
if (this.matchesSingle(atomic_value, t, atomic_class, assertionValue)) return true; // any type matches
if (this.matchesSingle(atomic_value, t, atomic_class, assertionValue, throwInvalidAssertion)) return true; // any type matches
}
return false;
}
// and
if (against instanceof Conjunction) {
for (const t of against) {
if (!this.matchesSingle(atomic_value, t, atomic_class, assertionValue)) return false; // any type does not match
if (!this.matchesSingle(atomic_value, t, atomic_class, assertionValue, throwInvalidAssertion)) return false; // any type does not match
}
return true;
}
// not
if (against instanceof Negation) {
return !this.matchesSingle(atomic_value, against.not(), atomic_class, assertionValue)
return !this.matchesSingle(atomic_value, against.not(), atomic_class, assertionValue, throwInvalidAssertion)
}

// assertion
if (against instanceof Assertion) {
const res = against.assert(assertionValue, undefined, true);
const res = against.assert(assertionValue, undefined, !throwInvalidAssertion);
if (res instanceof Promise) throw new RuntimeError("async assertion cannot be evaluated in logical connective");
return res
}
Expand Down
18 changes: 16 additions & 2 deletions types/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,12 +402,21 @@ export class Type<T = any> extends ExtensibleFunction {
}

// match type against template
/**
* @deprecated, use assertPropertyValueAllowed
*/
public isPropertyValueAllowed(property:any, value:any) {
if (!this.#template) return true;
else if (typeof property !== "string") return true; // only strings handled by templates
else return (!this.#template[property] || Type.matches(value, this.#template[property])) // check if value allowed
}

public assertPropertyValueAllowed(property:any, value:any) {
if (!this.#template) return true;
else if (typeof property !== "string") return true; // only strings handled by templates
else if (this.#template[property]) Type.assertMatches(value, this.#template[property]) // assert value allowed
}

// get type for value in template
public getAllowedPropertyType(property:any):Type {
if (!this.#template) return Type.std.Any;
Expand Down Expand Up @@ -640,8 +649,8 @@ export class Type<T = any> extends ExtensibleFunction {

// type check (type is a subtype of matches_type)
// TODO: swap arguments
public static matchesType(type:type_clause, against: type_clause, assertionValue?:any) {
return Logical.matches(type, against, Type, assertionValue);
public static matchesType(type:type_clause, against: type_clause, assertionValue?:any, throwInvalidAssertion = false) {
return Logical.matches(type, against, Type, assertionValue, throwInvalidAssertion);
}


Expand All @@ -656,6 +665,11 @@ export class Type<T = any> extends ExtensibleFunction {
}
}

public static assertMatches<T extends Type>(value:RefOrValue<any>, type:type_clause): asserts value is (T extends Type<infer TT> ? TT : any) {
const res = Type.matchesType(Type.ofValue(value), type, value, true);
if (!res) throw new ValueError("Value must be of type " + type)
}

// check if root type of value matches exactly
public static matches<T extends Type>(value:RefOrValue<any>, type:type_clause): value is (T extends Type<infer TT> ? TT : any) {
value = Ref.collapseValue(value, true, true);
Expand Down

0 comments on commit f1e4f88

Please sign in to comment.