<!-- AUTO-GENERATED — do not edit this file directly.
     Source of truth: conformance/suite/rules/security.py
     To regenerate:  uv run atlan-application-sdk-conformance gen-rule-docs
     To check CI staleness: uv run atlan-application-sdk-conformance gen-rule-docs --check -->

# Security / Secret-Hygiene Rules (S-series)

**2 rules** · Checker: `suite.checks.security` (AST-based)

Suppress a finding on the violating line or the line directly above it:

```python
# conformance: ignore[S002] intentional: platform self-auth, no SDK seam
```

**Rule-id stability (non-migration policy):** rule ids are a permanent public contract —
each is exposed in the SARIF `help_uri` and referenced by inline `# conformance:
ignore[...]` suppressions across the fleet.  An id therefore **never migrates and never
changes**, even if a future domain series (S/B/T/A/…) later subsumes the same topic.
When a domain series takes over an area, the rule is retired in place (kept documented,
no longer firing) and the new rule gets a fresh id — the original id is never reused or
reassigned.

| ID | Name | Tier | Scope | Category | Autofixable | Since |
|---|---|---|---|---|---|---|
| [S001](#s001) | `HardcodedCredential` | `warn` | `both` | `credential-storage` | — | 0.4.0 |
| [S002](#s002) | `RawEnvCredentialAccess` | `warn` | `app` | `credential-resolution` | — | 0.4.0 |

---

## S001 — `HardcodedCredential` {#s001}

**Tier:** `warn` · **Scope:** `both` · **Category:** `credential-storage` · **Autofixable:** — · **Since:** 0.4.0

> String literal assigned to a credential-named variable/argument — a hardcoded secret

**Rationale:** A credential baked into source is committed to version control in plaintext, shared with
everyone who can read the repo, survives rotation, and cannot be scoped or revoked
per-deployment. Secrets must be resolved at runtime through the SDK secret store, never
embedded in the code that ships them.

A non-empty string literal is assigned to (or passed as) a target whose name marks it a
credential value (`password`, `api_key`, `secret`, `access_key`, `client_secret`,
`token`, …).  Hardcoded credentials are committed in plaintext and cannot be rotated or
scoped per deployment.

Resolve the secret at runtime instead — via `context.resolve_credential(ref)` / a
`CredentialRef` (`application_sdk.credentials`) or the `SecretStore` protocol
(`application_sdk.infrastructure.secrets`).

The check is deliberately conservative: empty strings, `Field(default=…)` declarations,
format/URL templates (`"...{password}..."`), values that are themselves SCREAMING_SNAKE
env-var-name references, and `Enum` members are not flagged.  A reviewed exception is
suppressed inline with a justification: `# conformance: ignore[S001] <reason>`
(BLDX-1419).

---

## S002 — `RawEnvCredentialAccess` {#s002}

**Tier:** `warn` · **Scope:** `app` · **Category:** `credential-resolution` · **Autofixable:** — · **Since:** 0.4.0

> Credential-named environment variable read directly via os.getenv/os.environ

**Rationale:** Reading a credential straight from os.environ bypasses the SDK's secret-store seam:
there is no central audit of which secrets an app consumes, no typed CredentialRef
contract, and no Dapr-backed resolution. Apps must resolve secrets through the SDK
mechanism so credential handling stays uniform and auditable.

Application code reads a credential-named environment variable directly
(`os.getenv("...SECRET")`, `os.environ["...TOKEN"]`, `os.environ.get("...API_KEY")`)
instead of resolving it through the SDK secret store.  Raw env reads bypass the typed
`CredentialRef` contract and the Dapr-backed `SecretStore`
(`application_sdk.infrastructure.secrets`), so there is no central record of which
secrets an app consumes.

This rule is **app-scoped**: the SDK itself is the *provider* of the seam —
`EnvironmentSecretStore` legitimately reads `os.environ` — so it never fires on the SDK.
Environment *writes* (`os.environ[x] = v`), endpoint URLs (`..._TOKEN_URL`), public
identifiers (`..._ACCESS_KEY_ID`), store-name reads (`SECRET_STORE_NAME`), and dev
harnesses (`run_dev*.py`, `scripts/`) are not flagged.  A reviewed exception — e.g.
platform self-auth with no SDK seam — is suppressed inline: `# conformance: ignore[S002]
<reason>` (BLDX-1419).

---
