Back to skill

Security audit

AuditClaw Idp

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to do the promised identity-provider compliance checks, but it needs Review because its setup can expose highly privileged Google Workspace and Okta access and stores detailed user security posture locally.

Install only after creating dedicated least-privilege Google Workspace and Okta service identities, removing the unused Google audit-report scope, restricting OKTA_ORG_URL to your exact tenant, protecting and rotating tokens, and accepting that the local GRC database will contain identifiable user security-posture data.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/idp_evidence.py:298
Finding
Okta API Token Can Be Transmitted to an Arbitrary HTTPS Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/idp_evidence.py:161-165`, `scripts/idp_evidence.py:298-305`; the resulting configuration is consumed by all Okta check modules **Vulnerability Type**: Insufficient destination validation before transmitting credentials **Risk Level**: High ### Vulnerable Code ```python def _build_okta_config(): """Build the Okta client configuration dict.""" return { "orgUrl": os.environ["OKTA_ORG_URL"], "token": os.environ["OKTA_API_TOKEN"], } ``` ```python org_url = os.environ["OKTA_ORG_URL"].rstrip("/") if not org_url.startswith("https://"): print(json.dumps({"status": "error", "message": "OKTA_ORG_URL must use HTTPS"})) sys.exit(1) token = os.environ["OKTA_API_TOKEN"] headers = {"Authorization": f"SSWS {token}", "Accept": "application/json"} # Test user endpoint resp = requests.get(f"{org_url}/api/v1/users?limit=1", headers=headers, timeout=10) ``` ### Technical Analysis The application obtains both the Okta organization URL and API token from environment variables. It only verifies that the URL string begins with `https://`; it does not parse the URL or verify that the hostname is the expected Okta tenant. Consequently, any HTTPS server can be configured as `OKTA_ORG_URL`. The connection test directly sends the SSWS token in the `Authorization` header to that server. The same unrestricted `orgUrl` and token are passed to the Okta SDK for normal checks. HTTPS protects the connection in transit but does not establish that the destination is authorized to receive the credential. Prefix validation also fails to reject unexpected ports, embedded URL credentials, misleading hostnames, or attacker-controlled non-Okta domains. ### Attack Path 1. An attacker gains the ability to modify `OKTA_ORG_URL` in the Skill's environment or deployment configuration. 2. The legitimate `OKTA_API_TOKEN` remains configured. 3. The attacker sets `OKTA_ORG_URL` to an attacker-controlled HTTPS ...[truncated 953 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the URL with `urllib.parse.urlsplit` rather than using string-prefix validation. 2. Require: - Scheme exactly equal to `https` - No embedded username or password - No fragment or unexpected query - An explicitly approved port - A hostname matching the configured tenant allowlist 3. Require administrators to configure the expected tenant hostname separately and compare it exactly before attaching credentials. 4. Consider restricting standard Okta deployments to documented Okta domain suffixes, while supporting custom domains only through an explicit allowlist. 5. Never follow redirects to a different origin while retaining the `Authorization` header. 6. Prefer a scoped OAuth service application over an inherited-permission SSWS token. 7. Add automated tests covering attacker-controlled domains, deceptive subdomains, userinfo URLs, alternate ports, redirects, and malformed URLs. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
SKILL.md:97
Finding
Unused Google Audit-Report Scope Is Requested During Setup<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:97-100`, `scripts/idp-permissions.json:22-31`; implementation comparison at `scripts/idp_evidence.py:152-156` **Vulnerability Type**: Excessive domain-wide delegated permission **Risk Level**: Medium ### Vulnerable Configuration ```text **Step 3: Grant OAuth Scopes** In Google Admin → Security → API controls → Domain-wide delegation, add the service account with: - `https://www.googleapis.com/auth/admin.directory.user.readonly` - `https://www.googleapis.com/auth/admin.reports.audit.readonly` ``` The permission manifest similarly declares: ```json "oauth_scopes": [ { "scope": "https://www.googleapis.com/auth/admin.directory.user.readonly", "reason": "Read user directory: MFA status, login times, password strength" }, { "scope": "https://www.googleapis.com/auth/admin.reports.audit.readonly", "reason": "Read admin audit reports for security event monitoring" } ] ``` However, the implementation only requests the directory scope: ```python SCOPES = ["https://www.googleapis.com/auth/admin.directory.user.readonly"] creds = service_account.Credentials.from_service_account_file( os.environ["GOOGLE_WORKSPACE_SA_KEY"], scopes=SCOPES ) delegated = creds.with_subject(os.environ["GOOGLE_WORKSPACE_ADMIN_EMAIL"]) ``` ### Technical Analysis The setup documentation directs administrators to pre-authorize both the directory-read scope and the Admin Reports audit-read scope through Google Workspace domain-wide delegation. None of the eight implemented checks uses the Reports API, and runtime credentials request only `admin.directory.user.readonly`. Domain-wide delegation authorizes the service-account client at the organization level. Granting an unused scope therefore establishes a latent capability that is not necessary for the declared implementation. The fact that the current code does not request the scope reduces immediate exposure, but it does not eliminate the excessive aut ...[truncated 1066 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `https://www.googleapis.com/auth/admin.reports.audit.readonly` from `SKILL.md`. 2. Remove the same scope from `scripts/idp-permissions.json`. 3. Instruct existing users to revoke the unused scope from domain-wide delegation. 4. Keep only `https://www.googleapis.com/auth/admin.directory.user.readonly` for the current implementation. 5. If an audit-report check is added later, document its data use and request the scope only when that check is explicitly enabled. 6. Add a test that compares documented permissions with scopes requested by the implementation. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
SKILL.md:108
Finding
Okta Setup Can Produce an Overprivileged Inherited-Permission Token<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:108-113`, `scripts/idp-permissions.json:42-74` **Vulnerability Type**: Failure to enforce least-privileged Okta credentials **Risk Level**: Medium ### Vulnerable Documentation ```text ### Okta Setup **Step 1: Create API Token** Okta Admin → Security → API → Tokens → Create Token. Name: auditclaw-scanner **Step 2: Required Permissions** The token inherits the creating admin's permissions. Needs read access to: users, factors, policies. Scopes: `okta.users.read`, `okta.factors.read`, `okta.policies.read` ``` The permission manifest states: ```json "required_permissions": [ { "permission": "okta.users.read", "reason": "Read user profiles, MFA enrollment status, login activity" }, { "permission": "okta.factors.read", "reason": "Read MFA factor enrollment for each user" }, { "permission": "okta.policies.read", "reason": "Read password and session policies" } ], "note": "Okta API tokens inherit the permissions of the admin who creates them. Use a read-only admin or Super Admin with care." ``` ### Technical Analysis The Skill claims a read-only security model, but it instructs operators to create an SSWS API token. Such a token inherits the privileges of the administrator who creates it; listing desired read scopes in documentation does not technically limit an inherited-permission token. The permission manifest expressly permits use of a Super Admin account “with care.” A token created this way can retain substantially more authority than the user, factor, and policy reads required by the checks. The application does not introspect or enforce the token's effective privileges. Therefore, the runtime behavior may be read-only while the credential itself remains capable of write or administrative operations. ### Attack Path 1. An operator follows the setup guide using a Super Admin or another broadly privileged administrator. 2. Okta issues an SSWS token inheri ...[truncated 835 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not recommend creating the token as a Super Admin. 2. Require a dedicated service identity with a custom read-only administrator role. 3. Limit its resource set and permissions to the users, factors, and policies genuinely needed. 4. Prefer an Okta OAuth service application with explicit scopes over an inherited-permission SSWS token. 5. Document the exact provisioning procedure and effective privileges. 6. Add a startup warning or validation mechanism when the credential appears to have broader capabilities than required. 7. Rotate existing tokens created by privileged administrators after replacing them with least-privileged credentials. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/idp_evidence.py:62
Finding
Identifiable User Security Posture Is Persisted Without Data Minimization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/checks/google_admins.py:78-91`, `scripts/checks/google_inactive.py:83-106`, `scripts/checks/google_mfa.py:64-84`, `scripts/checks/google_passwords.py:63-77`, `scripts/checks/okta_mfa.py:63-84`, `scripts/checks/okta_inactive.py:79-102`; storage at `scripts/idp_evidence.py:62-75` and `scripts/idp_evidence.py:89-103` **Vulnerability Type**: Excessive plaintext retention of identity and account-security metadata **Risk Level**: Low ### Vulnerable Code For example, Google MFA findings associate an email address with MFA status: ```python for user in users: email = user.get("primaryEmail", "unknown") enrolled = user.get("isEnrolledIn2Sv", False) enforced = user.get("isEnforcedIn2Sv", False) if enrolled and enforced: findings.append({ "resource": f"google_workspace/user/{email}/mfa", "status": "pass", "detail": "2SV enrolled and enforced", }) elif enrolled and not enforced: findings.append({ "resource": f"google_workspace/user/{email}/mfa", "status": "fail", "detail": "2SV enrolled but not enforced", }) else: findings.append({ "resource": f"google_workspace/user/{email}/mfa", "status": "fail", "detail": "2SV not enrolled", }) ``` The entire result is passed as evidence content: ```python cmd = [ sys.executable, db_query, "--db-path", db_path, "--action", "add-evidence", "--control-id", control_id, "--type", "automated", "--source", "idp", "--description", f"IDP {check_name} check: {result['passed']}/{result['total']} passed", "--file-content", json.dumps(result, default=str), ] ``` The direct fallback also stores the complete result: ```python conn.execute( """INSERT INTO evidence (title, control_id, type, description, source, file_content, uploaded_at) VALUES (?, ?, 'automated', ...[truncated 1765 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store aggregate totals by default. 2. Store user-level identifiers only for failed findings when remediation requires them. 3. Pseudonymize identifiers using a keyed hash so repeated findings can be correlated without exposing email addresses. 4. Provide an explicit opt-in option for storing raw identities. 5. Enforce restrictive filesystem permissions on the database and its parent directory. 6. Define and implement evidence-retention and deletion policies. 7. Consider field-level encryption for user identifiers and security-posture details. 8. Avoid including provider exception bodies in persistent evidence unless they are sanitized for sensitive content. 9. Clearly document every identity and posture field retained by the Skill. ]]>

T08 · Insecure Dependencies

Note
Location
scripts/requirements.txt:1
Finding
Required Okta SDK Dependency Is Not Declared<![CDATA[ ## Vulnerability Details **File Location**: `scripts/requirements.txt:1-4`; imports at `scripts/checks/okta_mfa.py:9-12`, `scripts/checks/okta_inactive.py:10-13`, `scripts/checks/okta_passwords.py:12-15`, and `scripts/checks/okta_sessions.py:12-15` **Vulnerability Type**: Undeclared third-party dependency and non-reproducible installation **Risk Level**: Low ### Vulnerable Dependency Manifest ```text google-api-python-client==2.190.0 google-auth==2.48.0 google-auth-httplib2==0.3.0 requests==2.31.0 ``` The Okta checks nevertheless import an SDK that is absent from the manifest: ```python try: from okta.client import Client as OktaClient except ImportError: OktaClient = None ``` The missing client is later invoked: ```python try: client = OktaClient(okta_config) users, _, err = await client.list_users({"filter": 'status eq "ACTIVE"'}) if err: raise Exception(str(err)) except Exception as e: ``` ### Technical Analysis All four Okta modules depend on a package that provides `okta.client.Client`, but no such package is declared in `scripts/requirements.txt`. The documented installation therefore does not produce a complete environment for the advertised Okta checks. Although no malicious dependency is embedded in the repository, users may respond to the import failure by independently searching for and installing a similarly named package. This undermines dependency review and reproducibility and can increase exposure to dependency confusion, typosquatting, or incompatible SDK versions. The fallback sets `OktaClient` to `None`, after which invoking it produces a generic failure that is reported as an API failure rather than a clear missing-dependency error. ### Attack Path 1. An operator installs the declared requirements. 2. The operator runs an Okta check. 3. Importing `okta.client` fails because the required SDK was not installed. 4. The check produces an unclear failure, or the operator searches for a package manu ...[truncated 660 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add the official intended Okta SDK to `scripts/requirements.txt`. 2. Pin it to a reviewed exact version. 3. Prefer a hash-locked dependency file generated by a reproducible dependency-management tool. 4. Verify the package name, publisher, repository, and release integrity before inclusion. 5. Replace the `None` fallback with an explicit error explaining which reviewed package and version must be installed. 6. Add an installation test that imports every runtime dependency. 7. Generate and review a software bill of materials for all direct and transitive dependencies. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (9)

Tainted flow: 'headers' from os.environ (line 303, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
token = os.environ["OKTA_API_TOKEN"]
            headers = {"Authorization": f"SSWS {token}", "Accept": "application/json"}
            # Test user endpoint
            resp = requests.get(f"{org_url}/api/v1/users?limit=1", headers=headers, timeout=10)
            if resp.status_code == 200:
                results.append({"service": "Okta Auth", "status": "ok", "detail": "credentials valid"})
                results.append({"service": "Okta Users API", "status": "ok", "detail": "accessible"})
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill is described as performing read-only compliance checks, but the documented behavior includes writing evidence and integration status into a shared SQLite database and invoking an external helper to do so. This discrepancy can mislead operators into granting trust under a read-only assumption when the skill in fact mutates local state and interacts with another script, expanding the attack surface and enabling unintended data tampering or persistence.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README tells users to place sensitive credentials into environment variables, including an Okta API token and a path to a Google Workspace service-account key, but gives no guidance on secure handling, storage, rotation, or avoiding shell history and accidental disclosure. While using environment variables is common, documenting secret setup without safety warnings increases the chance of credential exposure through shared shells, process inspection, logs, screenshots, or copied command history.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises executable behavior with access to environment variables, network APIs, and shell execution, but does not declare an explicit tool scope such as permissions or allowed-tools. That omission weakens reviewability and enforcement because consumers cannot easily tell what capabilities the skill expects, increasing the risk of overbroad execution or hidden capability abuse if the implementation changes.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The security model claims Google Workspace uses only the admin.directory.user.readonly scope, while the setup instructions additionally require admin.reports.audit.readonly. Inconsistent scope documentation can cause underestimation of privilege, making reviewers and operators believe the integration is less sensitive than it really is and potentially approving broader access without proper scrutiny.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The manifest describes '8 read-only checks' across Google Workspace and Okta, which implies observation-only behavior. In addition to running checks, this script inserts evidence records into the database and updates integration status fields, which are state-changing write operations outside a purely read-only checking role.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"--file-content", json.dumps(result, default=str),
    ]

    proc = subprocess.run(cmd, capture_output=True, text=True)
    if proc.returncode != 0:
        return _store_evidence_direct(db_path, check_name, result)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Known Vulnerable Dependency: requests==2.31.0 — 6 advisory(ies): CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi); CVE-2026-25645 (Requests has Insecure Temp File Reuse in its extract_zipped_paths() utility func) +3 more

Medium
Category
Supply Chain
Confidence
96% confidence
Finding
The file pins requests to version 2.31.0, which is identified by the static analysis as having multiple published advisories, including credential leakage and TLS verification/session handling issues. In an identity-provider auditing skill that likely makes authenticated outbound HTTP requests to Google or Okta APIs, a vulnerable HTTP client library increases the risk of token, credential, or response-handling compromise.

Intent-Code Divergence

Low
Confidence
82% confidence
Finding
The top-level documentation describes running compliance checks and storing evidence, but omits that the script also mutates the integrations table by changing status, timestamps, and error counters. Because the documentation frames the behavior more narrowly than the code's actual state changes, it creates an intent/code mismatch for operators reviewing what the script affects.

Static analysis

No suspicious patterns detected.