Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/build_container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,11 @@ jobs:
# can do JSON extraction (reflection) and read OBP props (getenv); without it the sandbox
# denies these and DynamicResourceDocTest's native-execution scenarios fail.
echo 'dynamic_code_sandbox_permissions=[new java.net.NetPermission("specifyStreamHandler"), new java.lang.reflect.ReflectPermission("suppressAccessChecks"), new java.lang.RuntimePermission("getenv.*"), new java.util.PropertyPermission("cglib.useCache", "read"), new java.util.PropertyPermission("net.sf.cglib.test.stressHashCodes", "read"), new java.util.PropertyPermission("cglib.debugLocation", "read"), new java.lang.RuntimePermission("accessDeclaredMembers"), new java.lang.RuntimePermission("getClassLoader")]' >> obp-api/src/main/resources/props/test.default.props
# allow_user_generated_scala_code defaults to false everywhere (including test/dev)
# with no run-mode fallback; CI must opt in explicitly so DynamicUtilTest,
# ConnectorMethodTest, AbacRuleTests, DynamicResourceDocTest, DynamicMessageDocTest
# and DynamicCodeKillSwitchTest's ON scenarios can still compile/execute dynamic code.
echo allow_user_generated_scala_code=true >> obp-api/src/main/resources/props/test.default.props

- name: Run tests — shard ${{ matrix.shard }} (${{ matrix.name }})
run: |
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/build_pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,11 @@ jobs:
# can do JSON extraction (reflection) and read OBP props (getenv); without it the sandbox
# denies these and DynamicResourceDocTest's native-execution scenarios fail.
echo 'dynamic_code_sandbox_permissions=[new java.net.NetPermission("specifyStreamHandler"), new java.lang.reflect.ReflectPermission("suppressAccessChecks"), new java.lang.RuntimePermission("getenv.*"), new java.util.PropertyPermission("cglib.useCache", "read"), new java.util.PropertyPermission("net.sf.cglib.test.stressHashCodes", "read"), new java.util.PropertyPermission("cglib.debugLocation", "read"), new java.lang.RuntimePermission("accessDeclaredMembers"), new java.lang.RuntimePermission("getClassLoader")]' >> obp-api/src/main/resources/props/test.default.props
# allow_user_generated_scala_code defaults to false everywhere (including test/dev)
# with no run-mode fallback; CI must opt in explicitly so DynamicUtilTest,
# ConnectorMethodTest, AbacRuleTests, DynamicResourceDocTest, DynamicMessageDocTest
# and DynamicCodeKillSwitchTest's ON scenarios can still compile/execute dynamic code.
echo allow_user_generated_scala_code=true >> obp-api/src/main/resources/props/test.default.props

- name: Run tests — shard ${{ matrix.shard }} (${{ matrix.name }})
run: |
Expand Down
3 changes: 2 additions & 1 deletion obp-api/src/main/resources/props/sample.props.template
Original file line number Diff line number Diff line change
Expand Up @@ -1571,7 +1571,8 @@ personal_data_collection_consent_country_waiver_list = Austria, Belgium, Bulgari
# SECURITY: the runtime sandbox (SecurityManager) is a no-op on JDK 24+ (JEP 486) and the
# GraalVM JS context is created with HostAccess.ALL, so enabled dynamic code runs UNSANDBOXED.
# Leave false in production. Only enable on trusted sandbox/dev instances.
# (Always ON in test/dev run modes regardless of this value, unless this prop is set explicitly.)
# Defaults to false everywhere (including test/dev run modes) when this prop is absent —
# it must be set to true explicitly, there is no run-mode-based fallback.
allow_user_generated_scala_code=false

# enable dynamic code sandbox, default is false, this will make sandbox works for code running in Future, will make performance lower than disable
Expand Down
6 changes: 6 additions & 0 deletions obp-api/src/main/resources/props/test.default.props.template
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ allow_public_views =true
# the 10-thread concurrency tests. Set to 20 to provide headroom.
hikari.maximumPoolSize=20

# allow_user_generated_scala_code (RCE surface kill-switch) defaults to false everywhere,
# including test/dev, with no run-mode fallback. Set explicitly here so DynamicUtilTest,
# ConnectorMethodTest, AbacRuleTests, DynamicResourceDocTest, DynamicMessageDocTest and
# DynamicCodeKillSwitchTest's ON scenarios can compile/execute dynamic code locally.
allow_user_generated_scala_code=true

# Permissions granted to runtime-compiled dynamic-endpoint code inside the security sandbox.
# Mirrors default.props / production.default.props. Required so dynamic resource-doc bodies can do
# JSON extraction (reflection) and read OBP props (getenv); without it the sandbox denies these and
Expand Down
8 changes: 5 additions & 3 deletions obp-api/src/main/scala/code/api/util/DynamicUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ import scala.tools.reflect.{ToolBox, ToolBoxError}

object DynamicUtil extends MdcLoggable{

// Master kill-switch for user-generated dynamic code (RCE surface). Explicit prop always
// wins so tests can force OFF; absent the prop, ON in test/dev, OFF in production.
// Master kill-switch for user-generated dynamic code (RCE surface). Defaults to OFF
// everywhere — including test/dev — unless explicitly enabled via this prop. Tests that
// exercise dynamic-code compilation must set allow_user_generated_scala_code=true
// explicitly (see test.default.props / the CI "Setup props" step).
def dynamicCodeExecutionEnabled: Boolean =
APIUtil.getPropsValue("allow_user_generated_scala_code") match {
case Full(v) => v.toBoolean
case _ => net.liftweb.util.Props.testMode || net.liftweb.util.Props.devMode
case _ => false
}

val toolBox: ToolBox[universe.type] = runtimeMirror(getClass.getClassLoader).mkToolBox()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,26 @@ class DynamicCodeKillSwitchTest extends V400ServerSetup {
setPropsValues("connector" -> "star")
}

// The predicate defaults to false everywhere (including test/dev) with no run-mode
// fallback — it is false unless allow_user_generated_scala_code is explicitly set. This
// suite's baseline test.default.props sets it to true explicitly so the ON scenarios
// below can compile/execute dynamic code; there is no way, within this harness, to
// exercise the truly-absent case, since the base props file always supplies a value once
// set. The "absent -> false" branch is what protects deployers who never set the prop at
// all — it's covered by direct inspection of DynamicUtil.dynamicCodeExecutionEnabled's
// match expression, not by an integration test.
feature("DynamicUtil.dynamicCodeExecutionEnabled predicate") {

scenario("Defaults to enabled in test mode when the prop is absent", VersionOfApi) {
Then("the predicate should be true (we are running under testMode)")
scenario("Explicit prop=true (this suite's baseline) enables compilation", VersionOfApi) {
Then("the predicate should be true given the explicit test-props value")
DynamicUtil.dynamicCodeExecutionEnabled should be(true)

And("compileScalaCode should still compile and execute")
And("compileScalaCode should compile and execute")
val result = DynamicUtil.compileScalaCode[Int]("41 + 1")
result should be(Full(42))
}

scenario("Explicit prop=false overrides testMode and disables compilation", VersionOfApi) {
scenario("Explicit prop=false disables compilation regardless of run mode", VersionOfApi) {
setPropsValues("allow_user_generated_scala_code" -> "false")

Then("the predicate should be false")
Expand All @@ -80,10 +88,13 @@ class DynamicCodeKillSwitchTest extends V400ServerSetup {
result should be(Failure(DynamicCodeExecutionDisabled))
}

scenario("Explicit prop=true overrides a false default", VersionOfApi) {
scenario("A later explicit prop=true re-enables after being forced off", VersionOfApi) {
setPropsValues("allow_user_generated_scala_code" -> "false")
DynamicUtil.dynamicCodeExecutionEnabled should be(false)

setPropsValues("allow_user_generated_scala_code" -> "true")

Then("the predicate should be true")
Then("the predicate should be true again")
DynamicUtil.dynamicCodeExecutionEnabled should be(true)
}
}
Expand Down
6 changes: 6 additions & 0 deletions run_tests_parallel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,11 @@ run_shard() {
# OBP_DYNAMIC_CODE_SANDBOX_PERMISSIONS mirrors CI's dynamic_code_sandbox_permissions
# props line: without it the dynamic-code sandbox denies reflection/getenv and
# DynamicResourceDocTest's native-execution scenarios fail locally (CI green, local red).
# OBP_ALLOW_USER_GENERATED_SCALA_CODE mirrors CI's allow_user_generated_scala_code=true:
# the kill-switch defaults to false everywhere (including test/dev) with no run-mode
# fallback, so DynamicUtilTest / ConnectorMethodTest / AbacRuleTests /
# DynamicResourceDocTest / DynamicMessageDocTest / DynamicCodeKillSwitchTest's ON
# scenarios need this set explicitly or they fail locally with OBP-50020.
# -pl obp-commons,obp-api mirrors CI: obp-commons' own util suites run on whichever
# shard's filter matches com.openbankproject.* (the shard-4 catch-all); on every other
# shard the filter matches nothing in obp-commons -> 0 tests there.
Expand All @@ -291,6 +296,7 @@ run_shard() {
OBP_HTTP4S_TEST_PORT="${http4s_port}" \
OBP_MAIL_TEST_MODE="true" \
OBP_DYNAMIC_CODE_SANDBOX_PERMISSIONS='[new java.net.NetPermission("specifyStreamHandler"), new java.lang.reflect.ReflectPermission("suppressAccessChecks"), new java.lang.RuntimePermission("getenv.*"), new java.util.PropertyPermission("cglib.useCache", "read"), new java.util.PropertyPermission("net.sf.cglib.test.stressHashCodes", "read"), new java.util.PropertyPermission("cglib.debugLocation", "read"), new java.lang.RuntimePermission("accessDeclaredMembers"), new java.lang.RuntimePermission("getClassLoader")]' \
OBP_ALLOW_USER_GENERATED_SCALA_CODE="true" \
OBP_API_INSTANCE_ID="shard_${n}" \
"$TIMEOUT_BIN" 1200 mvn scalatest:test -pl obp-commons,obp-api -DfailIfNoTests=false \
"-DwildcardSuites=${filter}" \
Expand Down
Loading