-
Notifications
You must be signed in to change notification settings - Fork 23
feat: add Tornado checker #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
699a0b6
86f3473
812b3a0
9d11215
30fdbdc
d46215b
e61be5f
f32f9fb
2067880
b08bead
d23f87b
9f58641
fbd5978
6c0fcd4
4aad909
92b1953
dc7583b
b96962a
3cceb62
c8ff9dc
39669e6
8d19c17
61c8b5f
f84b50f
15da4c1
2682e6a
fee55b1
a1721c2
f4428da
0d63baf
e20920a
8244780
73ae7ef
6f50c2a
d3ac17a
3ec065b
e94be76
a8d9c60
fe20571
9c4d96f
8bd420d
b9f0fa1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,262 @@ | ||
| const PythonTaintAbstractChecker = require('./python-taint-abstract-checker') | ||
| const Config = require('../../../config') | ||
| const completeEntryPoint = require('../common-kit/entry-points-util') | ||
| const { markTaintSource } = require('../common-kit/source-util') | ||
| const { isTornadoCall, tornadoSourceAPIs, isRequestAttributeAccess } = require('./tornado-util') | ||
| const { extractRelativePath } = require('../../../util/file-util') | ||
|
|
||
| // Metadata storage | ||
| const tornadoRoutesMap = new WeakMap<any, any>() | ||
| const tornadoRouteMap = new WeakMap<any, any>() | ||
|
|
||
| /** | ||
| * Tornado Taint Checker - Simplified | ||
| */ | ||
| class TornadoTaintChecker extends PythonTaintAbstractChecker { | ||
| /** | ||
| * | ||
| * @param resultManager | ||
| */ | ||
| constructor(resultManager: any) { | ||
| super(resultManager, 'taint_flow_python_tornado_input') | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param analyzer | ||
| * @param scope | ||
| * @param node | ||
| * @param state | ||
| * @param info | ||
| */ | ||
| triggerAtStartOfAnalyze(analyzer: any, scope: any, node: any, state: any, info: any): void { | ||
| this.addSourceTagForcheckerRuleConfigContent('PYTHON_INPUT', this.checkerRuleConfigContent) | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param analyzer | ||
| * @param scope | ||
| * @param node | ||
| * @param state | ||
| * @param info | ||
| */ | ||
| triggerAtFunctionCallBefore(analyzer: any, scope: any, node: any, state: any, info: any): void { | ||
| super.triggerAtFunctionCallBefore(analyzer, scope, node, state, info) | ||
| const { fclos, argvalues } = info | ||
| if (Config.entryPointMode === 'ONLY_CUSTOM' || !fclos || !argvalues) return | ||
| const isApp = isTornadoCall(node, 'Application') | ||
| const isRouter = isTornadoCall(node, 'RuleRouter') | ||
| const isAdd = isTornadoCall(node, 'add_handlers') | ||
| if (isApp || isRouter || isAdd) { | ||
| let routes: any = null | ||
| if (isApp || isRouter) { | ||
| const isInit = ['__init__', '_CTOR_'].includes(node.callee?.property?.name || node.callee?.name) | ||
| routes = (isInit && argvalues[1]) || argvalues[0] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Short-circuit fallback incorrectly uses
|
||
| } else { | ||
| routes = argvalues[1] // isAdd case | ||
| } | ||
| if (routes) { | ||
| this.registerRoutesFromValue(analyzer, scope, state, routes) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Register routes from a collection value (List/Dict/Union/Single Symbol) | ||
| * @param analyzer | ||
| * @param scope | ||
| * @param state | ||
| * @param val | ||
| */ | ||
| private registerRoutesFromValue(analyzer: any, scope: any, state: any, val: any) { | ||
| if (!val) return | ||
| // 1. Handle recording optimization (tornadoRoute) | ||
| if (tornadoRouteMap.has(val)) { | ||
| const handler = tornadoRouteMap.get(val) | ||
| if (handler) { | ||
| this.finishRoute(analyzer, scope, state, handler) | ||
| return | ||
| } | ||
| } | ||
| // 2. Handle Union (often represents a flattened tuple) | ||
| if (val.vtype === 'union' && Array.isArray(val.value)) { | ||
| const handler = val.value.find((v: any) => v.vtype === 'class' || v.vtype === 'symbol' || v.vtype === 'fclos') | ||
| if (handler) { | ||
| this.finishRoute(analyzer, scope, state, handler) | ||
| } | ||
| val.value.forEach((v: any) => this.registerRoutesFromValue(analyzer, scope, state, v)) | ||
| return | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Union handler double-processes handlers causing duplicate source scopesMedium Severity In There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bugbot Autofix determined this is a false positive. The referenced Tornado union-route handling code does not exist in this branch, so the duplicate-processing scenario cannot occur here. This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Union handler processed twice via recursive callMedium Severity When There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Union handler processed twice causing duplicate source entriesMedium Severity In There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Union handler gets processed twice causing duplicate entrypointsMedium Severity When handling a union value, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bugbot Autofix determined this is a false positive. The referenced Tornado route-registration logic is absent because the target checker file is not in this codebase state. This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard. |
||
| } | ||
| // 3. Handle raw tuple (path, handler) | ||
| if (val.value && typeof val.value === 'object') { | ||
| const handler = val.value['1'] | ||
| if (handler) { | ||
| const pathArg = val.value['0'] | ||
| const path = pathArg?.value || pathArg?.ast?.value | ||
| if (typeof path === 'string') { | ||
| this.finishRoute(analyzer, scope, state, handler) | ||
| return | ||
| } | ||
| } | ||
| } | ||
| // 4. Handle direct class/symbol (likely a result of recursion or flattened tuple) | ||
| if (val.vtype === 'class' || val.vtype === 'symbol' || val.vtype === 'fclos') { | ||
| this.finishRoute(analyzer, scope, state, val) | ||
| return | ||
| } | ||
| // 5. Handle Collections (List/Object with numeric keys) | ||
| const isObject = (val.vtype === 'object' || !val.vtype) && val.value | ||
| if (isObject) { | ||
| const items = Array.isArray(val.value) ? val.value : Object.values(val.value) | ||
| const isLikelyCollection = Array.isArray(val.value) || Object.keys(val.value).some((k) => /^\d+$/.test(k)) | ||
| if (isLikelyCollection) { | ||
| items.forEach((item: any) => this.registerRoutesFromValue(analyzer, scope, state, item)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param analyzer | ||
| * @param scope | ||
| * @param state | ||
| * @param h | ||
| */ | ||
| private finishRoute(analyzer: any, scope: any, state: any, h: any) { | ||
| if (!h) return | ||
| if (h.vtype === 'union' && Array.isArray(h.value)) h = h.value[0] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Union handler only processes first element, missing othersMedium Severity When a handler is a union type (e.g., from conditional assignment like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Union handler type only registers first element's entry pointsMedium Severity When the handler There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty union array access causes undefined reference errorMedium Severity In |
||
| // 1. Check for recorded nested routes (Application/Router instances) | ||
| const innerRoutes = tornadoRoutesMap.get(h) || (h.value && tornadoRoutesMap.get(h.value)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty union causes crash in
|
||
| if (innerRoutes) { | ||
| this.registerRoutesFromValue(analyzer, scope, state, innerRoutes) | ||
| return | ||
| } | ||
| // 2. Handle Class Definition (Handler classes) | ||
| let cls = h | ||
| if (cls.vtype !== 'class' && cls.ast?.type === 'ClassDefinition') { | ||
| try { | ||
| cls = analyzer.processInstruction(scope, cls.ast, state) || this.buildClassSymbol(cls.ast) | ||
| } catch (e) { | ||
| cls = this.buildClassSymbol(cls.ast) | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Silent exception swallowing without loggingLow Severity The catch block silently swallows the exception without logging. Other Python taint checkers in the codebase use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Caught exception silently swallowed without loggingLow Severity The catch block captures exception |
||
| } else if (cls.vtype === 'symbol' && cls.cdef) { | ||
| // If it's an instance symbol, get its class definition | ||
| cls = cls.cdef | ||
| } | ||
| if (cls && (cls.vtype === 'class' || cls.vtype === 'symbol')) { | ||
| this.registerEntryPoints(analyzer, cls) | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing ONLY_CUSTOM check in triggerAtFunctionCallAfterThe |
||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing ONLY_CUSTOM mode check in triggerAtFunctionCallAfterThe |
||
|
|
||
| /** | ||
| * | ||
| * @param analyzer | ||
| * @param cls | ||
| */ | ||
| private registerEntryPoints(analyzer: any, cls: any) { | ||
| const methods = ['get', 'post', 'put', 'delete', 'patch'] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing HTTP methods compared to Django checkerLow Severity The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing HTTP methods head and options in entry point registrationMedium Severity The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing HTTP methods in Tornado entry point detectionMedium Severity The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing HTTP methods in entry point registrationLow Severity The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing HTTP methods head and options from handlersMedium Severity The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing head and options HTTP methods in entrypointsLow Severity The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing
|
||
| // Look for methods in cls.value, cls.field, or cls.value.field (Python specificity) | ||
| const classValue = cls.value?.field || cls.field || cls.value || {} | ||
| Object.entries(classValue).forEach(([name, fclos]: [string, any]) => { | ||
| if (methods.includes(name)) { | ||
| const ep = completeEntryPoint(fclos) | ||
| if (ep) { | ||
| analyzer.entryPoints.push(ep) | ||
| const actualParams = (fclos.fdef?.parameters || fclos.ast?.parameters || []) as any[] | ||
| actualParams.forEach((p: any) => { | ||
| const pName = p.id?.name || p.name | ||
| if (pName === 'self') return | ||
| // Add source scope for all non-self parameters | ||
| this.sourceScope.value.push({ | ||
| path: pName, | ||
| kind: 'PYTHON_INPUT', | ||
| scopeFile: extractRelativePath(fclos?.ast?.loc?.sourcefile || ep.filePath, Config.maindir), | ||
| scopeFunc: ep.functionName, | ||
| locStart: p.loc?.start?.line, | ||
| locEnd: p.loc?.end?.line, | ||
| }) | ||
| }) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Source scopes registered even for duplicate entry pointsMedium Severity The code that registers source scopes (lines 180-198) executes regardless of whether the entry point is a duplicate. The duplicate check at lines 171-178 only guards the |
||
| } | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param node | ||
| */ | ||
| private buildClassSymbol(node: any) { | ||
| const value: any = {} | ||
| node.body?.forEach((m: any) => { | ||
| if (m.type === 'FunctionDefinition') { | ||
| const name = m.id?.name || m.name?.name | ||
| if (name) { | ||
| value[name] = { | ||
| vtype: 'fclos', | ||
| fdef: m, | ||
| ast: m, | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| return { vtype: 'class', value, ast: node } | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param analyzer | ||
| * @param scope | ||
| * @param node | ||
| * @param state | ||
| * @param info | ||
| */ | ||
| triggerAtFunctionCallAfter(analyzer: any, scope: any, node: any, state: any, info: any): void { | ||
| super.triggerAtFunctionCallAfter(analyzer, scope, node, state, info) | ||
| const { fclos, ret, argvalues } = info | ||
| if (Config.entryPointMode === 'ONLY_CUSTOM' || !fclos || !ret) return | ||
| const name = node.callee?.property?.name || node.callee?.name | ||
| // 1. Record route info for Rule, URLSpec, url (Recording phase) | ||
| const isRuleCall = isTornadoCall(node, 'Rule') || isTornadoCall(node, 'URLSpec') || name === 'url' | ||
| if (isRuleCall && argvalues && argvalues.length >= 2) { | ||
| const handler = argvalues[1] | ||
| tornadoRouteMap.set(ret, handler) | ||
| } | ||
| // 2. Record internal routes for Application/RuleRouter instances | ||
| const isInit = ['__init__', '_CTOR_'].includes(name) | ||
| if (isInit && argvalues && argvalues.length >= 2) { | ||
| const self = argvalues[0] | ||
| const routes = argvalues[1] | ||
| // Heuristic: if routes looks like a list/tuple of routes | ||
| const isRouteList = | ||
| routes && (routes.vtype === 'object' || routes.vtype === 'symbol' || Array.isArray(routes.value)) | ||
| if (isRouteList && self) { | ||
| tornadoRoutesMap.set(self, routes) | ||
| } | ||
| } | ||
| const isApp = isTornadoCall(node, 'Application') | ||
| const isRouter = isTornadoCall(node, 'RuleRouter') | ||
| if (!isInit && (isApp || isRouter)) { | ||
| tornadoRoutesMap.set(ret, argvalues[0]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing null check for
|
||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing null check for
|
||
| if (tornadoSourceAPIs.has(name)) { | ||
| markTaintSource(ret, { path: node, kind: 'PYTHON_INPUT' }) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param analyzer | ||
| * @param scope | ||
| * @param node | ||
| * @param state | ||
| * @param info | ||
| */ | ||
| triggerAtMemberAccess(analyzer: any, scope: any, node: any, state: any, info: any): void { | ||
| if (Config.entryPointMode !== 'ONLY_CUSTOM' && isRequestAttributeAccess(node)) { | ||
| markTaintSource(info.res, { path: node, kind: 'PYTHON_INPUT' }) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing null check before marking taint sourceMedium Severity The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing null check before marking taint on undefined resultMedium Severity In |
||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing null check before marking taint sourceMedium Severity The |
||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Member access hook not invoked for Python analyzerHigh Severity The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing null check causes crash on unresolved member accessMedium Severity The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing null check before marking taint sourceMedium Severity The |
||
| } | ||
|
|
||
| export = TornadoTaintChecker | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Tornado Source APIs | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| export const tornadoSourceAPIs = new Set([ | ||||||||||||||||||||||||||||||||||
| 'get_argument', | ||||||||||||||||||||||||||||||||||
| 'get_query_argument', | ||||||||||||||||||||||||||||||||||
| 'get_body_argument', | ||||||||||||||||||||||||||||||||||
| 'get_query_arguments', | ||||||||||||||||||||||||||||||||||
| 'get_body_arguments', | ||||||||||||||||||||||||||||||||||
| 'get_cookie', | ||||||||||||||||||||||||||||||||||
| 'get_secure_cookie', | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+5
to
+11
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not resolved yet |
||||||||||||||||||||||||||||||||||
| 'get_arguments', | ||||||||||||||||||||||||||||||||||
| 'get_json_body', | ||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Detect if node is an access to a Tornado request attribute | ||||||||||||||||||||||||||||||||||
| * @param node | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| export function isRequestAttributeAccess(node: any): boolean { | ||||||||||||||||||||||||||||||||||
| if (node?.type !== 'MemberAccess') return false | ||||||||||||||||||||||||||||||||||
| const inner = node.object | ||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||
| inner?.type === 'MemberAccess' && | ||||||||||||||||||||||||||||||||||
| inner.object?.type === 'Identifier' && | ||||||||||||||||||||||||||||||||||
| inner.object?.name === 'self' && | ||||||||||||||||||||||||||||||||||
| inner.property?.name === 'request' && | ||||||||||||||||||||||||||||||||||
| [ | ||||||||||||||||||||||||||||||||||
| 'body', | ||||||||||||||||||||||||||||||||||
| 'query', | ||||||||||||||||||||||||||||||||||
| 'headers', | ||||||||||||||||||||||||||||||||||
| 'cookies', | ||||||||||||||||||||||||||||||||||
| 'files', | ||||||||||||||||||||||||||||||||||
| 'uri', | ||||||||||||||||||||||||||||||||||
| 'path', | ||||||||||||||||||||||||||||||||||
| 'arguments', | ||||||||||||||||||||||||||||||||||
| 'remote_ip', | ||||||||||||||||||||||||||||||||||
| 'host', | ||||||||||||||||||||||||||||||||||
| 'query_arguments', | ||||||||||||||||||||||||||||||||||
| 'body_arguments', | ||||||||||||||||||||||||||||||||||
| ].includes(node.property?.name) | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Check if node is a Tornado Application call | ||||||||||||||||||||||||||||||||||
| * @param node | ||||||||||||||||||||||||||||||||||
| * @param targetName | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| export function isTornadoCall(node: any, targetName: string): boolean { | ||||||||||||||||||||||||||||||||||
| if (!node || node.type !== 'CallExpression') return false | ||||||||||||||||||||||||||||||||||
| const { callee } = node | ||||||||||||||||||||||||||||||||||
| const funcName = callee.property?.name || callee.name | ||||||||||||||||||||||||||||||||||
| const objectName = callee.object?.name || callee.object?.property?.name | ||||||||||||||||||||||||||||||||||
| if (funcName === targetName || objectName === targetName) { | ||||||||||||||||||||||||||||||||||
| return true | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (['__init__', '_CTOR_'].includes(funcName)) { | ||||||||||||||||||||||||||||||||||
| let current = callee.object | ||||||||||||||||||||||||||||||||||
| while (current) { | ||||||||||||||||||||||||||||||||||
| const currentName = current.name || current.property?.name | ||||||||||||||||||||||||||||||||||
| if (currentName === targetName) return true | ||||||||||||||||||||||||||||||||||
| current = current.object || current.callee | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Init fallback incorrectly treats self as routes
Medium Severity
The expression
(isInit && argvalues[1]) || argvalues[0]has incorrect fallback behavior. WhenisInitis true (an__init__call whereargvalues[0]isself), ifargvalues[1]is falsy (e.g., no handlers argument), this falls back toargvalues[0], incorrectly passingselfas routes intoregisterRoutesFromValue. A ternary likeisInit ? argvalues[1] : argvalues[0]would correctly yieldundefined(caught by theif (routes)guard) instead ofself.