BookmarkSubscribeRSS Feed

Governing Python and R Access in SAS Viya CAS with extlang.xml

Started ‎08-20-2026 by
Modified ‎08-20-2026 by
Views 62

SAS Cloud Analytic Services (CAS) can do more than run native CAS actions, it can also invoke external languages like Python and R directly from a CAS session, through actions such as dataStep.runCode or the python/R language extensions. This is a powerful capability: it lets data scientists blend open-source code with distributed, in-memory CAS processing.

Configuring extlang.xml is a core step when setting up Python and R integration in SAS Viya, serving as the primary control point for governing user access to external language interpreters. This Kubernetes-native deployment method applies seamlessly to both brand-new SAS Viya 4 environments and existing running clusters.

Powerful capabilities like that need guardrails. By default, CAS runs as a shared service account, so allowing any authenticated user to execute arbitrary Python or R code inside a CAS session is effectively allowing arbitrary code execution under that service identity. SAS Viya addresses this with a single, declarative control point: the extlang.xml file.

This post explains what extlang.xml does, how its structure enforces a security model, and walks through a practical, Kubernetes-native way to create and deploy it in a SAS Viya 4 deployment.

 

The Problem It Solves

 

Without an explicit policy, a CAS deployment has to make an all-or-nothing decision about external language execution: either no one can run Python/R from CAS, or everyone can. Neither extreme fits a real environment, where you typically want to:

  • Allow a small set of trusted users or groups (for example, platform administrators or a specific analytics team) to run external code
  • Restrict how they run it, which interpreter binary, which environment variables, which scratch disk
  • Prevent everyone else from touching external languages at all, even if they can authenticate to Viya

extlang.xml gives CAS a single, auditable file that encodes exactly this policy, evaluated per user and per group, before any external language process is launched.

 

How SAS Viya Approaches It

 

CAS locates its policy file through the SAS_EXTLANG_SETTINGS environment variable, which points to a path such as:

/opt/sas/viya/home/sas-pyconfig/extlang.xml

That path needs to live somewhere CAS can actually read, which is why SAS Viya deployments typically dedicate a persistent volume claim (PVC), often named sas-pyconfig, to hold both the external language runtimes (Python, R) and this policy file.

 

Before creating or changing the file, confirm that your deployment already uses the target environment variable by inspecting the CAS controller pod with kubectl exec:

CAS_POD=sas-cas-server-default-controller
CONTAINER=sas-cas-server

SETTINGS_PATH=$(kubectl -n gelenv exec "$CAS_POD" -c "$CONTAINER" -- printenv SAS_EXTLANG_SETTINGS)
echo "SAS_EXTLANG_SETTINGS = ${SETTINGS_PATH}"

If this returns nothing, the environment variable isn't set for that pod, and CAS won't enforce any extlang.xml policy at all; external language access falls back to whatever CAS's built-in default is. The path shown above /opt/sas/viya/home/sas-pyconfig/extlang.xml is only the common default; your deployment may point SAS_EXTLANG_SETTINGS at a different directory, and that directory may be backed by a PVC with a different name than sas-pyconfig. Don't assume either, confirm both before you author or copy anything.

 

Two things are worth calling out about the access model:

  • Identity requirement: any user referenced in extlang.xml must already exist in the CASHostAccountRequired identity group. This ties external-language execution to users who have a recognized host account, not just a Viya logon identity.
  • Default-deny posture: the top-level mode="ALLOW" combined with allowAllUsers="BLOCK" means the file as a whole permits external language use, but only for users and groups explicitly listed. Everyone else is blocked by default. This is the safer of the two possible defaults, and it's the pattern the example below uses.

 

Key Components and Flow

 

The extlang.xml file is organized into a top-level <EXTLANG> wrapper containing a <DEFAULT> baseline block and optional <GROUP> override blocks.

 

Configuration Hierarchy

 

Global Baseline: <EXTLANG> -> <DEFAULT> -> <LANGUAGE>

Group Overrides: <EXTLANG> -> <GROUP> -> <LANGUAGE>

Important: despite its name, <GROUP> has nothing to do with SAS Viya custom groups, LDAP groups, or Active Directory groups. It is purely a local policy label inside this XML file, you can call it anything you like. The users attribute is what ties the rule to actual users, listed by their host account names. If a <GROUP> block says name="analysts" users="Henrik,Delilah", CAS doesn't look up an "analysts" group anywhere; it simply applies that block's permissions to Henrik and Delilah individually.

Tip: To see a complete, working extlang.xml file before diving into the individual settings, jump down to the “kubectl cp into a disposable utility pod” section below.

 

The file is organized into a <DEFAULT> block and one or more <GROUP> blocks:

Element / Attribute Purpose
<EXTLANG mode, allowAllUsers> Global switch and default-deny posture for the whole file
<DEFAULT> Baseline settings applied to every session before group-level rules are layered on
scratchDisk Directory external language processes use for temporary files
diskAllowlist Paths the external process is permitted to read/write beyond the scratch disk
userSetScratchDisk Whether an end user can override the scratch disk (ALLOW/BLOCK)
<LANGUAGE name="PYTHON3"\ > Per-language configuration, nested inside <DEFAULT> or <GROUP>
interpreter Full path to the interpreter binary CAS should launch
userSetEnv Whether users can set their own environment variables for the language process
userSetInterpreter Whether users can point to a different interpreter than the configured one
userInlineCode Whether users can submit inline Python/R code (as opposed to only pre-approved scripts)
<GROUP name="..." users="..."> Logical label for a policy block; the users attribute ties it to named users, layered on top of <DEFAULT>

The evaluation flow is additive: <DEFAULT> establishes the locked-down baseline (fixed interpreter, no user overrides), and each <GROUP> block loosens specific permissions for its members, never the reverse. This is a classic least-privilege pattern: start closed, open up deliberately for named groups.

 

Example Scenario: Creating the File

 

extlang.xml just needs to land at whatever path SAS_EXTLANG_SETTINGS points to, on whatever PVC backs that path, which won't necessarily be /opt/sas/viya/home/sas-pyconfig on a PVC named sas-pyconfig, even though that's the common default shown in SAS documentation. Before writing anything, resolve the real mount path and PVC name for your deployment directly from $SETTINGS_PATH. To do this, you will “get” the PVC and volume mounts information looking at the “sas-cas-server” definition using kubectl:

MOUNT_PATH=$(dirname "$SETTINGS_PATH")

VOLUME_NAME=$(kubectl -n gelenv get pod "$CAS_POD" -o jsonpath='{.spec.containers[?(@.name=="'"$CONTAINER"'")].volumeMounts[?(@.mountPath=="'"$MOUNT_PATH"'")].name}')
PVC_NAME=$(kubectl -n gelenv get pod "$CAS_POD" -o jsonpath='{.spec.volumes[?(@.name=="'"$VOLUME_NAME"'")].persistentVolumeClaim.claimName}')

echo "MOUNT_PATH = ${MOUNT_PATH}"
echo "PVC_NAME   = ${PVC_NAME}"

This takes the directory portion of $SETTINGS_PATH as MOUNT_PATH (true as long as extlang.xml sits directly at the volume's mount point, which is the standard layout) then looks up the volume mounted at exactly that path and resolves its PVC claim name. If your deployment nests the file under a subdirectory of the actual mount (uncommon), you'll need to recursively check parent directories to find the real mount point instead.

 

This walks the CAS container's volumeMounts, finds the one whose path is a prefix of $SETTINGS_PATH (the longest match wins, in case one mount is nested inside another), then resolves that volume's actual PVC claim name. MOUNT_PATH and PVC_NAME are now set correctly for your environment, whatever it's called, and the rest of this walkthrough uses them instead of hardcoded defaults.

 

It's tempting to kubectl cp straight into the CAS controller pod, since it already has the PVC mounted, but that mount is deliberately read-only on the CAS side (CAS shouldn't be able to rewrite its own interpreter/policy volume at runtime), so the copy fails:

tar: extlang.xml: Cannot open: Read-only file system
command terminated with exit code 2

The fix is to copy into a small, disposable pod that mounts the same PVC read-write instead, then delete it once the file is in place.

 

kubectl cp into a disposable utility pod

 

The SAS administrator should author extlang.xml as a plain, standalone file; editable, diffable, and committable in version control just like any other config. Here's a starting point you can copy, save locally as extlang.xml, and adjust to match your own users, groups, and, if MOUNT_PATH differs from the default, your own interpreter and scratch disk paths:

<EXTLANG version="1.0" mode="ALLOW" allowAllUsers="BLOCK">
  <DEFAULT scratchDisk="/tmp"
           diskAllowlist="/opt/sas/viya/home/sas-pyconfig"
           userSetScratchDisk="BLOCK">
    <LANGUAGE name="PYTHON3"
              interpreter="/opt/sas/viya/home/sas-pyconfig/default_py/bin/python3"
              userSetEnv="BLOCK"
              userSetInterpreter="BLOCK" />
    <LANGUAGE name="R"
              interpreter="/opt/sas/viya/home/sas-pyconfig/default_r/bin/Rscript"
              userSetEnv="BLOCK"
              userSetInterpreter="BLOCK" />
  </DEFAULT>
  <GROUP name="PowerUsers" users="sasadm">
    <LANGUAGE name="PYTHON3"
              userInlineCode="ALLOW"
              userSetEnv="ALLOW"
              userSetInterpreter="ALLOW" />
    <LANGUAGE name="R"
              userInlineCode="ALLOW"
              userSetEnv="ALLOW"
              userSetInterpreter="ALLOW" />
  </GROUP>
  <GROUP name="analysts" users="Henrik,Delilah">
    <LANGUAGE name="PYTHON3" userInlineCode="ALLOW" />
    <LANGUAGE name="R" userInlineCode="ALLOW" />
  </GROUP>
</EXTLANG>

This example locks the interpreter and scratch disk for everyone, then loosens permissions for two named groups: PowerUsers gets full control over environment and interpreter selection, while analysts (named users Henrik and Delilah) can only submit inline code with the fixed, locked-down interpreter. Adjust the group names, users, and languages to match your own environment.

Note that PowerUsers and analysts above are arbitrary labels chosen for readability, they are not looked up in Viya, LDAP, or Active Directory. Only the users attribute matters for access control.

 

Next, launch a minimal pod that mounts $PVC_NAME read-write at $MOUNT_PATH. It doesn't need to run anything beyond staying alive long enough to receive the file (the sleep 3600 keeps it active for 1 hour) :

kubectl -n gelenv delete pod debug-sas-pyconfig --ignore-not-found
kubectl -n gelenv apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: sas-pyconfig-writer
spec:
  securityContext:
    fsGroup: 1001
    runAsGroup: 1001
    runAsNonRoot: true
    runAsUser: 1001
    seccompProfile:
      type: RuntimeDefault
  restartPolicy: Never
  containers:
    - name: alpine
      image: alpine:3.20
      command: ["sleep", "3600"]
      volumeMounts:
        - name: pyconfig
          mountPath: ${MOUNT_PATH}
  volumes:
    - name: pyconfig
      persistentVolumeClaim:
        claimName: ${PVC_NAME}
EOF

 

Copy the file into the new pod, which in turn writes it directly to the mounted persistent volume. Once the file is written, remove the pod, it was only a temporary vehicle for writing the file, not a long-running workload.

# extlang.xml is the file you just saved locally, e.g. checked into your Viya config repo
kubectl -n gelenv cp ./extlang.xml "sas-pyconfig-writer:${MOUNT_PATH}/extlang.xml"

kubectl -n gelenv delete pod sas-pyconfig-writer

 

Verifying and Applying the Change

 

Confirm the file landed correctly and with the right ownership:

kubectl -n gelenv exec "$CAS_POD" -c "$CONTAINER" -- ls -alF "$MOUNT_PATH"

You should see extlang.xml alongside the default_py / default_r interpreter symlinks already provisioned on the volume. Reading from the CAS controller's read-only mount works fine; only writes to it are blocked. New and existing CAS sessions will pick up the new or updated policy.

 

Summary and Key Takeaways

 

  • extlang.xml is CAS's policy file for governing external language execution, it's read from the path set in SAS_EXTLANG_SETTINGS. Confirm that path and the PVC backing it rather than assuming the documented defaults; both can be customized per deployment.
  • The recommended default posture is mode="ALLOW" with allowAllUsers="BLOCK": closed by default, opened explicitly per group or user.
  • <DEFAULT> sets the locked-down baseline; <GROUP> is only a local policy label and it selectively loosens specific permissions, never the reverse.
  • Every referenced user must belong to CASHostAccountRequired.
  • Use kubectl cp to copy a locally-authored extlang.xml into a short-lived utility pod that mounts the same PVC read-write, then delete the pod. The CAS controller itself mounts it read-only, so writing directly into it fails. The file stays a plain, diffable artifact throughout.

 

Further Reading

 

 

Find more articles from SAS Global Enablement and Learning here.

Contributors
Version history
Last update:
‎08-20-2026 04:11 AM
Updated by:

Viya Copilot Motion Graphic.gifViya Copilot Motion Graphic

Ready to see what SAS Viya Copilot can do?

Visit the Tips & Tricks page for setup guidance, demos, and practical examples that show how Copilot supports your workflows.

Get Started →

SAS AI and Machine Learning Courses

The rapid growth of AI technologies is driving an AI skills gap and demand for AI talent. Ready to grow your AI literacy? SAS offers free ways to get started for beginners, business leaders, and analytics professionals of all skill levels. Your future self will thank you.

Get started

Article Tags