Snyk Studio and Secure at Inception
The Snyk quickstart guides for MCP enable you to install and configure Snyk Studio in your preferred coding assistant, such as Cursor, in minutes. To scale this process at the company level, you can roll out the Secure at Inception ruleset broadly with as little friction as possible.
Secure at Inception is a configurable option that prompts the LLM to scan code for security issues during generation in the IDE. Developers can manually run Snyk security scans to add custom rules to ensure secure code at inception, or can enable the Secure at Inception option at the company level.
Below is a request using Windsurf and Claude to generate a login page.

Claude then uses Snyk’s MCP tools to validate the security of the code it just wrote due to deployed environmental rules.

In this case, a vulnerability was found:

A fix is suggested:

Secure at Inception configuration
Using the VS Code Snyk IDE extension, the Secure at Inception option introduced settings that automatically set up Snyk Studio.

The rules of Secure at Inception are as follows:
Always run the Snyk Code scanning tool for new first-party code generated.
Always run the Snyk SCA scanning tool for new dependencies or dependency updates.
Always run the Snyk IaC scanning tool for new Infrastructure as Code updates.
If security issues arise from new or modified code or dependencies, attempt to fix them using the results context from Snyk.
Rescan the code after fixing issues to verify they are resolved and no new issues are introduced.
Repeat this process until no issues are found.
Snyk offers reference scripts for deploying the IDE extension on developer machines with default settings. You can customize these scripts as needed.
It is possible that these scripts need your input and customization.
Prerequisites
Ensure that:
You have an active Snyk account
Access to an Endpoint Management tool, such as JAMF
Snyk testing was performed on Mac with VSCode, Cursor and Windsurf using JAMF as the MDM. Further testing will be conducted on other operating systems (such as Windows), additional MDMs (such as Intune), and additional IDEs.
Deploy the Secure at Inception option
For broad deployment, use an MDM or Endpoint Management tools like JAMF to target MCP configuration to developer devices.
For JAMF, targeting for Group and configurations must be separated as follows:
For machines with Windsurf installed, target Snyk’s Windsurf MCP scripts to deploy.
For machines with Cursor installed, target Snyk’s Cursor MCP scripts to deploy.
Where neither is installed, deploy the desired .pkg and configure matching scripts to be run after installation.
You can configure the MDM to re-apply the desired Secure at Inception configuration across all endpoints on a regular basis.
Sample scripts
Install the Snyk extension and configure the Snyk MCP server
#!/bin/bash
set -euo pipefail
readonly INSTALL_PLUGIN_ID="snyk-security.snyk-vulnerability-scanner"
readonly REMOVE_PLUGIN_ID="snyk-security.snyk-vulnerability-scanner-preview"
# Set by find_editor_cmd
EDITOR_CMD=""
# Set by fix_zscaler_cert_path
CERT_FILE=""
# --- Logging helpers ---
log_info() { echo "[INFO] $1"; }
log_error() { echo "[ERROR] $1" >&2; }
# -----------------------
# --- Console user helpers ---
get_console_user() { stat -f%Su /dev/console; }
get_console_home() {
local user
user="$(get_console_user)"
launchctl asuser "$(id -u "$user")" sudo -u "$user" /bin/sh -lc 'printf %s "$HOME"'
}
run_as_user() {
local user uid
user="$(get_console_user)"
uid="$(id -u "$user")"
launchctl asuser "$uid" sudo -u "$user" "$@"
}
# ----------------------------
# --- PATH setup ---
build_path() {
local editor_paths="$1"
local user_home="$(get_console_home)"
echo "${editor_paths}:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
}
# Find a usable editor (absolute paths first, then PATH)
find_editor_cmd() {
local name="$1"
local paths="$2"
IFS=':' read -ra path_array <<< "$paths"
for path in "${path_array[@]}"; do
local candidate="${path}/$name"
if run_as_user test -x "$candidate"; then
EDITOR_CMD="$candidate"
return 0
fi
done
local user_path resolved
user_path="$(build_path "$paths")"
if resolved=$(run_as_user /bin/sh -lc "PATH='${user_path}':\"\\$PATH\"; command -v $name 2>/dev/null || true"); then
if [[ -n "$resolved" ]]; then
EDITOR_CMD="$resolved"
return 0
fi
fi
return 1
}
# ------------------
# --- Zscaler certificate fix ---
fix_zscaler_cert_path() {
# Check if a Zscaler root certificate is present in the system keychain
local zscaler_certs
if zscaler_certs=$(security find-certificate -c "Zscaler Root CA" -p /Library/Keychains/System.keychain 2>/dev/null); then
log_info "Zscaler certificate found in keychain."
# Create a temporary file to hold the certificate
CERT_FILE=$(run_as_user mktemp --tmpdir=/tmp zscaler-cert-XXXXXXXXXX.pem)
if ! run_as_user printf %s "${zscaler_certs}" > "$CERT_FILE"; then
log_error "Failed to export Zscaler certificate to a temporary file."
exit 1
fi
log_info "Exported certificate to a temporary file for use during installation."
# Set a trap to ensure the temporary file is removed on exit
trap 'run_as_user rm -f "$CERT_FILE"' EXIT
fi
}
# -------------------------------
# --- Plugin functions ---
editor_installed() {
local name="$1"
local paths="$2"
if find_editor_cmd "$name" "$paths"; then
if run_as_user "${EDITOR_CMD}" --version >/dev/null 2>&1; then
log_info "${name} installed (using: ${EDITOR_CMD})"
return 0
fi
fi
log_info "${name} not installed, skipping"
return 1
}
plugin_installed() {
local name="$1" extensions_output
if ! extensions_output=$(run_as_user "${EDITOR_CMD}" --list-extensions 2>/dev/null); then
log_error "Failed to list extensions via ${EDITOR_CMD}"
exit 1
fi
if echo "${extensions_output}" | grep -qx "${INSTALL_PLUGIN_ID}"; then
log_info "Snyk plugin already installed in ${name}, skipping"
return 0
fi
log_info "Snyk plugin not yet installed in ${name}"
return 1
}
install_plugin() {
local name="$1" paths="$2"
local user_home extdir output rc user_path
user_home="$(get_console_home)"
user_path="$(build_path "$paths")"
extdir="${user_home}/.${name}/extensions"
run_as_user /bin/mkdir -p "${extdir}"
log_info "Installing Snyk plugin for ${name}"
log_info "Removing stable extension, if present"
run_as_user /bin/sh -lc \
"HOME='${user_home}' PATH='${user_path}' \
'${EDITOR_CMD}' --uninstall-extension '${REMOVE_PLUGIN_ID}'" 2>&1 || true
# Fix the SSL certificate issue if Zscaler is present
fix_zscaler_cert_path
local install_cmd="HOME='${user_home}' PATH='${user_path}' '${EDITOR_CMD}' --extensions-dir '${extdir}' --install-extension '${INSTALL_PLUGIN_ID}' --force"
# Add the Zscaler certificate to the environment if it was found
if [[ -n "$CERT_FILE" ]]; then
install_cmd="NODE_EXTRA_CA_CERTS='$CERT_FILE' $install_cmd"
log_info "Using temporary Zscaler certificate for installation."
else
log_info "Zscaler certificate not found, proceeding without certificate override."
fi
# Run the installation command
output=$(run_as_user /bin/sh -lc "$install_cmd" 2>&1) || rc=$?
if [[ -z "${rc:-}" || "${rc}" -eq 0 ]]; then
log_info "Plugin installed for ${name}"
return 0
fi
log_error "Install failed (exit ${rc}) for ${name}:"
printf '%s\n' "${output}" >&2
exit 1
}
# --------------------------
# --- Main execution ---
main() {
log_info "Starting Snyk plugin installation script"
ide_paths=(
"windsurf|$(get_console_home)/.codeium/windsurf/bin:/Applications/Windsurf.app/Contents/Resources/app/bin"
"cursor|/Applications/Cursor.app/Contents/Resources/app/bin"
)
for ide in "${ide_paths[@]}"; do
IFS='|' read -r name paths <<< "$ide"
log_info "Processing ${name}..."
if ! editor_installed "$name" "$paths"; then
continue
fi
if plugin_installed "$name"; then
continue
fi
install_plugin "$name" "$paths"
log_info "${name} processing complete"
done
log_info "Snyk plugin installation script completed successfully"
}
main "$@"Enable Secure at Inception by applying the rules in the Project
#!/bin/bash
set -u -o pipefail
log_info(){ echo "[INFO] $1"; }
log_error(){ echo "[ERROR] $1" >&2; }
# --- console user helpers ---
get_console_user(){ stat -f%Su /dev/console; }
get_console_home(){
local user; user="$(get_console_user)"
launchctl asuser "$(id -u "$user")" sudo -u "$user" /bin/sh -lc 'printf %s "$HOME"'
}
run_as_user(){
local user uid; user="$(get_console_user)"; uid="$(id -u "$user")"
launchctl asuser "$uid" sudo -u "$user" "$@"
}
set_key(){
local plistbuddy="/usr/libexec/PlistBuddy"
local file_path="$1"
local full_key_path="$2"
run_as_user "$plistbuddy" -c "Delete :$full_key_path" "$file_path" 2>/dev/null || true
if ! run_as_user "$plistbuddy" -c "Add :$full_key_path bool true" "$file_path"; then
log_error "Failed to set $full_key_path in $file_path"
return 1
fi
log_info "Successfully set $full_key_path = true in $file_path"
return 0
}
update_json(){
local file_path="$1"
local parent_key="snyk.securityAtInception"
local all_keys=("autoConfigureMcpServer" "publishSecurityAtInceptionRules")
local cursor_key="persistRulesInProjects"
if [[ "$file_path" == *"Cursor"* ]]; then
all_keys+=("$cursor_key")
fi
# Ensure parent dir + file
run_as_user /bin/mkdir -p "$(dirname "$file_path")"
if ! run_as_user test -f "$file_path"; then
log_info "Config file missing; creating"
run_as_user /bin/sh -lc "printf '{}' > \"${file_path}\""
fi
# Convert whatever is there to XML plist
if ! run_as_user /usr/bin/plutil -convert xml1 "$file_path" 2>/dev/null; then
# If conversion fails (corrupt file), reset to empty dict and convert again
run_as_user /bin/sh -lc "printf '{}' > \"${file_path}\""
run_as_user /usr/bin/plutil -convert xml1 "$file_path"
fi
local ok=0
for key in "${all_keys[@]}"; do
if ! set_key "$file_path" "$parent_key:$key"; then ok=1; fi
done
# Convert back to JSON
run_as_user /usr/bin/plutil -convert json -r "$file_path" || {
log_error "Failed to convert $file_path back to JSON"
return 1
}
return $ok
}
is_valid_json_target(){
local p="$1"
[[ -n "${p}" ]] || return 1
[[ "${p}" != "/" ]] || return 1
[[ "${p}" == *.json ]] || return 1
if run_as_user test -d "${p}"; then return 1; fi
return 0
}
process_config_file(){
local config_file="$1"
log_info "Processing configuration file: $config_file"
if ! is_valid_json_target "${config_file}"; then
log_error "Invalid target path: ${config_file}. Must be a .json file, not '/' or a directory."
return 1
fi
update_json "$config_file"
}
main(){
local user_home; user_home="$(get_console_home)"
local config_files=(
"${user_home}/Library/Application Support/Windsurf/User/settings.json"
"${user_home}/Library/Application Support/Cursor/User/settings.json"
)
local ok=0
for cf in "${config_files[@]}"; do
if ! process_config_file "$cf"; then ok=1; fi
done
exit $ok
}
main "$@"Below is an example of the Windsurf.pkg and both Snyk scripts ready to be deployed on devices using JAMF:

In order for the IDE and the MCP to be properly configured:
Ensure that The MCP server is present. In Windsurf, navigate to Windsurf > Settings > Advanced settings > Cascade > MCP Servers > Manage MCPs.

Ensure that the user is prompted to trust Snyk. The code scan does not work if trust is not provided.
If Secure at Inception is configured, ensure that the rules file snyk_rules.md includes the rules and that Activation Mode is set to Always On.

To learn more, see Troubleshooting for the Snyk MCP.
Last updated
Was this helpful?

