Skip to content

feat: implement MDM enrollment authentication system#834

Merged
yeisonvargasf merged 1 commit intopyupio:mainfrom
nikkipin:main
Feb 20, 2026
Merged

feat: implement MDM enrollment authentication system#834
yeisonvargasf merged 1 commit intopyupio:mainfrom
nikkipin:main

Conversation

@nikkipin
Copy link
Contributor

Adds machine-to-machine (MDM) authentication as a new auth pathway alongside existing OAuth2 and API key methods, enabling headless/managed device enrollment.

Core authentication:

  • MachineCredentialConfig: filelock-protected storage in auth.ini [machine] section
  • Platform-specific machine ID detection (Linux, macOS, Windows) with unified resolve_machine_id() layered resolution
  • MachineTokenAuth(httpx.Auth) with Basic Auth encoding
  • configure_auth_session() precedence: API key > OAuth2 > Machine token > Unauth
  • safety auth enroll <key> command (positional arg) with key validation and credential persistence; SAFETY_ENROLLMENT_KEY env var fallback
  • safety auth status extended for enrollment state display

Platform client & TLS:

  • SafetyPlatformClient with single-client-per-invocation model — each CLI invocation resolves to exactly one auth path (API key, OAuth2, or machine token)
  • Shared TLS probe (safety/utils/tls_probe.py) with certifi → system trust store fallback for all auth paths (OAuth2, machine-token, enrollment)
  • Enrollment HTTP logic consolidated into SafetyPlatformClient, reusing shared _build_client_kwargs() for headers, proxy, TLS, and timeout
  • SAFETY_PLATFORM_V2_URL config for enrollment and system-scan endpoints

Error handling:

  • Exit codes: 73 (non-retryable enrollment failure), 74 (machine ID unavailable), 75/EX_TEMPFAIL (retryable enrollment failure)
  • EnrollmentError, EnrollmentTransientFailure, MachineIdUnavailableError
  • 5xx → retryable, network errors after retry exhaustion → retryable
  • httpx.TransportError coverage (ReadError, WriteError, ProxyError, etc.)

System-scan fix:

  • Pass auth.platform.http_client (authenticated) instead of the bare OAuth2Client, fixing MissingTokenError on the machine token path

Config & env overrides:

  • get_config_setting() priority: env var → config file → enum default
  • Settings starting with SAFETY_ use their name directly as env var
  • Fix ENROLLMENT_ENDPOINT from /api/mdm/enroll/ to /api/enroll
  • Accept HTTP 201 for new enrollments; follow_redirects=True

Testing:

  • Integration test harness (tests/integration/conftest.py) with server health check, isolated auth storage, CLI runner, and auth session bypass
  • Enrollment integration tests covering happy path, already-enrolled, force re-enroll, invalid key, env var enrollment, credential persistence
  • Handle Click 8.2+ read-only Context.protected_args in tests
  • Fix _is_oauth2_flow_command() to use ctx.protected_args (not protected_params, which doesn't exist in Click)

Description

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Refactor
  • Other (please describe):

Related Issues

Testing

  • Tests added or updated
  • No tests required

Checklist

  • Code is well-documented
  • Changelog is updated (if needed)
  • No sensitive information (e.g., keys, credentials) is included in the code
  • All PR feedback is addressed

Additional Notes

@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@safety-bot
Copy link
Contributor

safety-bot commented Feb 20, 2026

🚀 Artifacts — PR #834 by @nikkipin (source: nikkipin/safety)

Security notice: You are viewing pre-release CI artifacts from PR #834 by @nikkipin (source: nikkipin/safety). These commands may execute code on your machine. Do NOT run them unless you have reviewed the PR diff and trust the source. The snippets include a confirmation prompt.

Download the wheel file and binaries with gh CLI or from the workflow artifacts.

📦 Install & Run

Pre-requisites

# Install uv if needed
curl -LsSf https://astral.sh/uv/install.sh | sh

# Create and enter artifacts directory
mkdir artifacts && cd artifacts

Quick Test with Python Package

bash -c 'set -euo pipefail; echo; echo "WARNING: You are about to download and execute CI artifacts from PR #834 by @nikkipin (source: nikkipin/safety). Do NOT proceed unless you have reviewed the PR diff and trust the source."; echo; read -rp "Type I understand to continue: " C; [ "$C" = "I understand" ] || { echo "Aborted."; exit 1; }; gh run download 22233727102 -n dist -R pyupio/safety; uvx safety-*-py3-none-any.whl --version'

Run other Safety commands as follows

uvx safety-*-py3-none-any.whl auth status
uvx safety-*-py3-none-any.whl auth login
uvx safety-*-py3-none-any.whl scan

Note: You need to be logged in to GitHub to access the artifacts.

@yeisonvargasf yeisonvargasf added the enhancement Suggests an improvement or new feature. label Feb 20, 2026
Copy link
Member

@yeisonvargasf yeisonvargasf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @nikkipin! This looks great 👍

There is only 1 comment blocking this merge (Changelog).

Adds machine-to-machine (MDM) authentication as a new auth pathway
alongside existing OAuth2 and API key methods, enabling headless/managed
device enrollment.

Core authentication:
- MachineCredentialConfig: filelock-protected storage in auth.ini [machine] section
- Platform-specific machine ID detection (Linux, macOS, Windows) with
  unified resolve_machine_id() layered resolution
- MachineTokenAuth(httpx.Auth) with Basic Auth encoding
- configure_auth_session() precedence: API key > OAuth2 > Machine token > Unauth
- `safety auth enroll <key>` command (positional arg) with key validation
  and credential persistence; SAFETY_ENROLLMENT_KEY env var fallback
- `safety auth status` extended for enrollment state display

Platform client & TLS:
- SafetyPlatformClient with single-client-per-invocation model — each CLI
  invocation resolves to exactly one auth path (API key, OAuth2, or machine token)
- Shared TLS probe (safety/utils/tls_probe.py) with certifi → system trust
  store fallback for all auth paths (OAuth2, machine-token, enrollment)
- Enrollment HTTP logic consolidated into SafetyPlatformClient, reusing
  shared _build_client_kwargs() for headers, proxy, TLS, and timeout
- SAFETY_PLATFORM_V2_URL config for enrollment and system-scan endpoints

Error handling:
- Exit codes: 73 (non-retryable enrollment failure), 74 (machine ID
  unavailable), 75/EX_TEMPFAIL (retryable enrollment failure)
- EnrollmentError, EnrollmentTransientFailure, MachineIdUnavailableError
- 5xx → retryable, network errors after retry exhaustion → retryable
- httpx.TransportError coverage (ReadError, WriteError, ProxyError, etc.)

System-scan fix:
- Pass auth.platform.http_client (authenticated) instead of the bare
  OAuth2Client, fixing MissingTokenError on the machine token path

Config & env overrides:
- get_config_setting() priority: env var → config file → enum default
- Settings starting with SAFETY_ use their name directly as env var
- Fix ENROLLMENT_ENDPOINT from /api/mdm/enroll/ to /api/enroll
- Accept HTTP 201 for new enrollments; follow_redirects=True

Testing:
- Integration test harness (tests/integration/conftest.py) with server
  health check, isolated auth storage, CLI runner, and auth session bypass
- Enrollment integration tests covering happy path, already-enrolled,
  force re-enroll, invalid key, env var enrollment, credential persistence
- Handle Click 8.2+ read-only Context.protected_args in tests
- Fix _is_oauth2_flow_command() to use ctx.protected_args (not
  protected_params, which doesn't exist in Click)
Copy link
Member

@yeisonvargasf yeisonvargasf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @nikkipin!

@yeisonvargasf yeisonvargasf merged commit 6c6a744 into pyupio:main Feb 20, 2026
38 checks passed
yeisonvargasf added a commit that referenced this pull request Feb 24, 2026
The CONTEXT_FEATURE_TYPE platform gate is temporarily commented out
because the new MDM enrollment auth (#834) does not yet support
server-side feature flags. Without this change, system-scan is
inaccessible to MDM-authenticated users since the feature flag
check fails silently and hides the command.

The gate should be re-enabled once feature flag support is added
to the MDM auth flow.
yeisonvargasf added a commit that referenced this pull request Feb 24, 2026
#837)

The CONTEXT_FEATURE_TYPE platform gate is temporarily commented out
because the new MDM enrollment auth (#834) does not yet support
server-side feature flags. Without this change, system-scan is
inaccessible to MDM-authenticated users since the feature flag
check fails silently and hides the command.

The gate should be re-enabled once feature flag support is added
to the MDM auth flow.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Suggests an improvement or new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants