test(security): assert aborts from the webout where there is no job state
Sites / deploy (push) Successful in 3m43s
Build / Build-and-ng-test (pull_request) Successful in 5m2s
Lighthouse Checks / lighthouse (pull_request) Successful in 20m48s
Build / Build-and-test-development (pull_request) Successful in 33m28s

The four security test programs proved that a bad input aborted the service by
reading work.results, the job table %mv_jobflow writes in the Viya branch of
%mx_execute. The SASjs Server and SAS 9 branches call %ms_testservice instead,
so the table never exists, the data step fails with "File WORK.RESULTS.DATA
does not exist." and the assertion reported (0=1) - a failure that says
nothing about whether the service validated its input.

The platform difference is now in one macro, dc_assert_abort: on Viya it reads
the job state as before, elsewhere it looks for the sasjsAbort block that
mp_abort leaves in the webout %mx_execute returns through outref. The call to
the service stays outside the platform check, so the payloads are submitted on
every platform and the assertion is real rather than a skip.

Verified on SASjs Server (sas.4gl.io, target sas719, appLoc /Public/app/dc719):
the seven abort assertions PASS, i.e. the services do abort on the macro, SQL
injection and path traversal payloads. Negative control: the same program with
a valid libds reports FAIL (0=1), so the assertion can still fail.

sasjs lint: exit 0, no warnings in the five files.
This commit is contained in:
dc committed 2026-10-26 20:17:50 +00:00
1 parent 0d58be975d
commit 36e168971c
5 files changed
+76 -63

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
/**
@file
@brief Asserts that a service aborted, on any platform
@details Records a PASS in the test results when the child program aborted.
The evidence used depends on the platform:
@li SASVIYA - the child runs as a job, so the state is read from work.results,
the table mv_jobflow writes for the request.
@li SAS 9 and SASjs Server - there is no job state to read, so the abort is
spotted from the sasjsAbort block that mp_abort leaves in the webout, which
%mx_execute returns through outref.
Usage:
%mx_execute(&_program,
viyacontext=&defaultcontext,
inputfiles=&f1:iwant,
outref=web1,
viyaresult=WEBOUT_TXT
)
%dc_assert_abort(desc=Bad libds aborts the service, outref=web1)
@param [in] desc The description recorded in the test results
@param [in] outref The fileref given to %mx_execute, holding the response
@param [out] outds= (work.test_results) Results dataset
<h4> SAS Macros </h4>
@li mf_getplatform.sas
@li mp_assert.sas
**/
%macro dc_assert_abort(desc, outref, outds=work.test_results);
%local aborted;
%let aborted=0;
%if %mf_getplatform()=SASVIYA %then %do;
/* the job state is the only record of an abort on Viya */
data _null_;
set work.results;
if state='canceled' then call symputx('aborted',1);
run;
%end;
%else %do;
/* no job state here - an aborted service returns the sasjsAbort block */
data _null_;
infile &outref;
input;
if index(_infile_,'sasjsAbort')>0 then call symputx('aborted',1);
run;
%end;
%mp_assert(
iftrue=(&aborted=1),
desc=&desc,
outds=&outds
)
%mend dc_assert_abort;
+3 -9
View File
@@ -12,6 +12,7 @@
assertion cannot pass against a service that does not validate.
<h4> SAS Macros </h4>
@li dc_assert_abort.sas
@li mp_assert.sas
@li mx_execute.sas
@li mf_getuniquefileref.sas
@@ -37,16 +38,9 @@ run;
viyaresult=WEBOUT_TXT
)
%let abort1=0;
data _null_;
set work.results;
if state='canceled' then call symputx('abort1',1);
run;
%mp_assert(
iftrue=(&abort1=1),
%dc_assert_abort(
desc=Macro content in libds aborts the service,
outds=work.test_results
outref=web1
)
/**
@@ -13,6 +13,7 @@
validate.
<h4> SAS Macros </h4>
@li dc_assert_abort.sas
@li mp_assert.sas
@li mx_execute.sas
@li mf_getuniquefileref.sas
@@ -38,16 +39,9 @@ run;
viyaresult=WEBOUT_TXT
)
%let abort1=0;
data _null_;
set work.results;
if state='canceled' then call symputx('abort1',1);
run;
%mp_assert(
iftrue=(&abort1=1),
%dc_assert_abort(
desc=Macro content in libds aborts the service,
outds=work.test_results
outref=web1
)
/**
@@ -67,16 +61,9 @@ run;
viyaresult=WEBOUT_TXT
)
%let abort2=0;
data _null_;
set work.results;
if state='canceled' then call symputx('abort2',1);
run;
%mp_assert(
iftrue=(&abort2=1),
%dc_assert_abort(
desc=Macro content in col aborts the service,
outds=work.test_results
outref=web2
)
/**
@@ -13,6 +13,7 @@
validate.
<h4> SAS Macros </h4>
@li dc_assert_abort.sas
@li mp_assert.sas
@li mx_execute.sas
@@ -38,16 +39,9 @@ run;
viyaresult=WEBOUT_TXT
)
%let abort1=0;
data _null_;
set work.results;
if state='canceled' then call symputx('abort1',1);
run;
%mp_assert(
iftrue=(&abort1=1),
%dc_assert_abort(
desc=Macro content in table aborts the service,
outds=work.test_results
outref=web1
)
/**
@@ -67,16 +61,9 @@ run;
viyaresult=WEBOUT_TXT
)
%let abort2=0;
data _null_;
set work.results;
if state='canceled' then call symputx('abort2',1);
run;
%mp_assert(
iftrue=(&abort2=1),
%dc_assert_abort(
desc=SQL injection in filter aborts the service,
outds=work.test_results
outref=web2
)
/**
@@ -98,16 +85,9 @@ run;
viyaresult=WEBOUT_TXT
)
%let abort3=0;
data _null_;
set work.results;
if state='canceled' then call symputx('abort3',1);
run;
%mp_assert(
iftrue=(&abort3=1),
%dc_assert_abort(
desc=Path traversal in table aborts the service,
outds=work.test_results
outref=web3
)
/**
@@ -12,6 +12,7 @@
assertion cannot pass against a service that does not validate.
<h4> SAS Macros </h4>
@li dc_assert_abort.sas
@li mp_assert.sas
@li mx_execute.sas
@li mf_getuniquefileref.sas
@@ -37,16 +38,9 @@ run;
viyaresult=WEBOUT_TXT
)
%let abort1=0;
data _null_;
set work.results;
if state='canceled' then call symputx('abort1',1);
run;
%mp_assert(
iftrue=(&abort1=1),
%dc_assert_abort(
desc=Macro content in filter_table aborts the service,
outds=work.test_results
outref=web1
)
/**