# Secure Coding Standard: [Organisation or product]

> Secure coding standard for application developers: scope and
> languages, mandatory rules by area (input validation, output encoding,
> authentication, session management, access control, cryptography,
> secrets, error handling and logging, dependency use, file handling,
> API security) with do and don't code examples, required tooling (SAST,
> DAST, secret scanning) and an exceptions process. The areas reflect
> widely used OWASP concepts (for example the OWASP Top 10 and ASVS
> topics); the rules and examples are original. Replace every
> `[bracketed]` value and adapt examples to your stack.

<!--
Guidance: How to use this template
- Keep rules short, testable and enforced by tooling wherever possible. A
  rule nobody can check becomes advice.
- Use MUST for mandatory rules and SHOULD for strong recommendations, and
  give every rule an ID so reviews and tools can cite it.
- Replace the examples with code from your own languages and frameworks;
  developers follow examples that look like their codebase.
- Review the standard yearly and after any incident caused by a coding
  weakness.
-->

| Field | Value |
|---|---|
| Document ID | [SCS-STD-001] |
| Version | [1.0] |
| Status | [Draft / In review / Approved] |
| Owner | [@appsec-lead] |
| Approvers | [@engineering-director], [@ciso] |
| Applies to | [All production code in repositories under `github.com/[org]`] |
| Effective date | YYYY-MM-DD |
| Next review | YYYY-MM-DD |

## 1. Purpose and scope

<!-- Guidance: Say who must follow the standard, for which code, and how it relates to other policies. Name the languages so readers know which examples apply. -->

This standard defines the minimum secure coding rules for software
written or maintained by [Organisation]. It applies to production code,
infrastructure as code and build scripts. Prototypes that never handle
real data are exempt until they are promoted.

| Language / stack | Frameworks | Example section owner |
|---|---|---|
| *Example:* TypeScript (Node.js 22 LTS) | Express, React | [@web-guild] |
| *Example:* Python 3.12 | FastAPI, SQLAlchemy | [@python-guild] |
| *Example:* Go 1.23 | net/http, sqlc | [@platform] |
| [Language] | [Frameworks] | [@owner] |

Rule keywords: **MUST** is mandatory and enforced in review; **SHOULD**
is expected unless a documented reason applies.

## 2. Input validation

<!-- Guidance: Validate at every trust boundary, on the server, against an allow-list of expected type, length, format and range. Client-side validation is for usability only. -->

| ID | Rule |
|---|---|
| IV-01 | Input from any source outside the process (HTTP, queues, files, environment) MUST be validated on the server against a schema. |
| IV-02 | Validation MUST use allow-lists (expected type, length, pattern, range), not block-lists of bad characters. |
| IV-03 | Unexpected fields SHOULD be rejected, and MUST NOT be bound automatically to internal objects (mass assignment). |

Do:

```ts
import { z } from "zod";
const CreateOrder = z.object({
  sku: z.string().regex(/^[A-Z0-9-]{3,32}$/),
  quantity: z.number().int().min(1).max(999),
}).strict(); // rejects unknown fields
const input = CreateOrder.parse(req.body);
```

Don't:

```ts
// Trusts every field the client sends, including "price" and "isAdmin"
const order = await db.order.create({ data: req.body });
```

## 3. Output encoding and injection prevention

<!-- Guidance: Encode for the interpreter that will read the data (HTML, SQL, shell, LDAP, URL). Use parameterised APIs so that data never becomes code. -->

| ID | Rule |
|---|---|
| OE-01 | Database queries MUST use parameterised statements or a query builder; string concatenation of input into queries is forbidden. |
| OE-02 | HTML output MUST be encoded by the template engine; raw HTML insertion requires sanitisation with an approved library. |
| OE-03 | Operating system commands MUST NOT be built from input; use library calls or argument arrays without a shell. |
| OE-04 | Dynamic code execution (`eval`, `Function`, expression languages) MUST NOT process input. |

Do:

```python
cur.execute("SELECT id, total FROM orders WHERE customer_id = %s", (customer_id,))
subprocess.run(["convert", src_path, dst_path], check=True)  # no shell
```

Don't:

```python
cur.execute(f"SELECT * FROM orders WHERE customer_id = '{customer_id}'")
os.system("convert " + filename + " out.png")
```

## 4. Authentication

<!-- Guidance: Use the organisation's identity provider and vetted libraries. Never build password storage, token formats or MFA yourself. -->

| ID | Rule |
|---|---|
| AU-01 | User authentication MUST go through [corporate IdP via OIDC]; custom login forms are not permitted for staff applications. |
| AU-02 | Where local passwords are unavoidable they MUST be hashed with [Argon2id] using library defaults or stronger, never with fast hashes. |
| AU-03 | Login, recovery and MFA endpoints MUST be rate limited and return the same response for unknown and known accounts. |
| AU-04 | Service-to-service calls MUST authenticate with workload identity or short-lived tokens, not shared static passwords. |

Do:

```python
from argon2 import PasswordHasher
ph = PasswordHasher()
stored = ph.hash(password)
ph.verify(stored, attempt)
```

Don't:

```python
stored = hashlib.md5(password.encode()).hexdigest()
```

## 5. Session management

<!-- Guidance: Sessions must be unguessable, protected in transit and storage, and invalidated on logout and privilege change. -->

| ID | Rule |
|---|---|
| SE-01 | Session cookies MUST set `Secure`, `HttpOnly` and `SameSite=Lax` or `Strict`, and SHOULD use the `__Host-` prefix. |
| SE-02 | Session identifiers MUST be regenerated after login and after privilege change. |
| SE-03 | Logout MUST invalidate the session on the server, not only delete the cookie. |
| SE-04 | Browser applications SHOULD NOT keep access or refresh tokens in `localStorage`. |

Do:

```ts
res.cookie("__Host-sid", sessionId, {
  secure: true, httpOnly: true, sameSite: "lax", path: "/", maxAge: 30 * 60 * 1000,
});
```

Don't:

```ts
localStorage.setItem("refreshToken", token); // readable by any injected script
```

## 6. Access control

<!-- Guidance: Check authorization on the server for every request, for both the function and the specific object. Deny by default. -->

| ID | Rule |
|---|---|
| AC-01 | Every endpoint MUST perform an authorization check on the server; the default MUST be deny. |
| AC-02 | Object access MUST verify the caller's relationship to the specific record (ownership, tenant, role), not only that the caller is logged in. |
| AC-03 | Authorization logic SHOULD live in one shared module or policy engine, not repeated per handler. |

Do:

```go
order, err := store.GetOrder(ctx, orderID)
if err != nil || order.TenantID != caller.TenantID {
    http.NotFound(w, r) // do not reveal existence
    return
}
```

Don't:

```go
order, _ := store.GetOrder(ctx, r.URL.Query().Get("id")) // any logged-in user, any order
```

## 7. Cryptography

<!-- Guidance: Use high-level, well-reviewed libraries and approved algorithms. Developers choose the library, not the cipher mode. -->

| ID | Rule |
|---|---|
| CR-01 | Only algorithms on the approved list MUST be used: [AES-256-GCM, ChaCha20-Poly1305, SHA-256 or stronger, Ed25519, ECDSA P-256, RSA 3072+]. |
| CR-02 | ECB mode, MD5 and SHA-1 for security purposes, and home-made schemes MUST NOT be used. |
| CR-03 | Random values for tokens, keys and IDs MUST come from a cryptographically secure generator. |
| CR-04 | Keys MUST be managed in [KMS / HSM]; application code receives handles or data keys, never master keys. |

Do:

```ts
import { randomBytes } from "node:crypto";
const resetToken = randomBytes(32).toString("base64url");
```

Don't:

```ts
const resetToken = Math.random().toString(36).slice(2); // predictable
```

## 8. Secrets

<!-- Guidance: Secrets never live in source code, images or logs. Prefer short-lived credentials issued at runtime. -->

| ID | Rule |
|---|---|
| SK-01 | Secrets MUST NOT be committed to repositories, including tests and history; secret scanning with push protection MUST be enabled. |
| SK-02 | Applications MUST read secrets at runtime from [secrets manager]; container images MUST NOT contain secrets. |
| SK-03 | A leaked secret MUST be revoked and rotated within [4 hours]; deleting the commit is not sufficient. |

Do:

```python
db_password = secrets_client.get_secret_value(SecretId="prod/payments/db")["SecretString"]
```

Don't:

```python
DB_PASSWORD = "Summer2026!"  # in source, in history, in every clone
```

## 9. Error handling and logging

<!-- Guidance: Fail closed, show generic errors to users, log enough for investigation and nothing that creates a new leak. -->

| ID | Rule |
|---|---|
| EL-01 | Errors returned to clients MUST be generic and carry a correlation ID; stack traces and queries MUST NOT reach clients. |
| EL-02 | Security events (login success and failure, access denied, privilege change, data export) MUST be logged with who, what, when and from where. |
| EL-03 | Logs MUST NOT contain passwords, tokens, full card numbers or unnecessary personal data. |
| EL-04 | Log entries built from input MUST be structured (JSON fields), not concatenated strings, to prevent log injection. |
| EL-05 | On error in a security check the code MUST deny access (fail closed). |

Do:

```ts
logger.warn({ event: "authz_denied", userId, resource: "order", orderId, requestId });
res.status(404).json({ type: "about:blank", title: "Not found", requestId });
```

Don't:

```ts
logger.info("Login for " + req.body.email + " with password " + req.body.password);
res.status(500).send(err.stack);
```

## 10. Dependency use

<!-- Guidance: Every dependency is code you ship. Choose carefully, pin, update and remove. Align with the supply chain security assessment. -->

| ID | Rule |
|---|---|
| DE-01 | New dependencies MUST pass the intake review in [link to intake process]. |
| DE-02 | Lockfiles MUST be committed and CI MUST install from them. |
| DE-03 | Dependencies with known critical or high vulnerabilities MUST be updated within [7 / 30] days or covered by an exception. |
| DE-04 | CI actions and base images SHOULD be pinned by commit SHA or digest. |

Do:

```yaml
- uses: actions/checkout@<full-40-character-commit-sha>  # v4.2.2
- run: npm ci
```

Don't:

```yaml
- uses: some-org/deploy-action@main
- run: npm install
```

## 11. File handling

<!-- Guidance: Treat file names, types and contents as untrusted. Store uploads outside the web root and never execute them. -->

| ID | Rule |
|---|---|
| FH-01 | Uploads MUST be limited in size and validated by content (magic bytes), not only by extension or client content type. |
| FH-02 | Stored files MUST use server-generated names; user-supplied paths MUST NOT reach file system calls. |
| FH-03 | Archives MUST be extracted with checks against path traversal and decompression bombs. |
| FH-04 | Downloads of user files SHOULD be served with `Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`. |

Do:

```python
safe_name = f"{uuid4()}.pdf"
dest = (UPLOAD_ROOT / safe_name).resolve()
assert dest.is_relative_to(UPLOAD_ROOT)
```

Don't:

```python
open(os.path.join("/srv/uploads", request.args["name"]), "wb").write(data)  # ../../etc
```

## 12. API security

<!-- Guidance: APIs are the main attack surface. Enforce schemas, limits and authorization per object, and avoid exposing more data than the client needs. -->

| ID | Rule |
|---|---|
| AP-01 | Every API MUST have an OpenAPI or GraphQL schema that is enforced at runtime. |
| AP-02 | Responses MUST return only the fields the client needs; internal fields MUST be excluded by an explicit response model. |
| AP-03 | APIs MUST apply rate limits per client and limits on page size, query depth and request body size. |
| AP-04 | CORS MUST allow only named origins; wildcard origins with credentials are forbidden. |
| AP-05 | Server-side requests to user-supplied URLs MUST go through an allow-list and block internal address ranges. |

Do:

```python
class OrderOut(BaseModel):
    id: str
    status: str
    total: Decimal  # no internal cost, no customer email

@app.get("/orders/{order_id}", response_model=OrderOut)
```

Don't:

```python
@app.get("/orders/{order_id}")
def get_order(order_id: str):
    return db.query(Order).get(order_id).__dict__  # every column, every tenant
```

## 13. Tooling and verification

<!-- Guidance: Name the tools, where they run and what blocks a merge. Rules enforced by tools are followed; rules enforced only by memory are not. -->

| Tool type | Tool | Runs | Blocking threshold | Rules covered |
|---|---|---|---|---|
| SAST | *Example:* CodeQL, Semgrep with org ruleset | Every pull request | New high or critical finding | IV, OE, CR, FH |
| Secret scanning | *Example:* Platform secret scanning with push protection; gitleaks in CI | Every push | Any verified secret | SK |
| Dependency scanning (SCA) | *Example:* Dependabot alerts, OSV-Scanner | Daily and on PR | Critical or high with fix available | DE |
| DAST | *Example:* OWASP ZAP baseline scan | Nightly on staging | High alerts | SE, AP, EL |
| Infrastructure as code scanning | [Tool] | Every PR | [Threshold] | [Rules] |
| Code review checklist | Pull request template | Every PR | Reviewer sign-off | All |

Coverage target: [100%] of production repositories run SAST, secret
scanning and SCA by YYYY-MM-DD.

## 14. Exceptions process

<!-- Guidance: Exceptions are allowed but visible, time-limited and owned. An exception without an expiry date is a permanent policy change. -->

A team that cannot meet a MUST rule requests an exception before merge:

1. Open a ticket in [SEC project] with the rule ID, the code location,
   the reason and the compensating controls.
2. The application security team assesses risk within [5 business days].
3. Approval authority: Medium risk by [@appsec-lead]; High risk by
   [@ciso].
4. Approved exceptions are recorded in the register below, linked from a
   code comment at the location, and expire after at most [6 months].

| ID | Rule | Repository / location | Reason | Compensating control | Approved by | Expires |
|---|---|---|---|---|---|---|
| EX-001 | *Example:* CR-02 | `legacy-sync/hash.py` | Partner API requires SHA-1 HMAC | Isolated service, partner IP allow-list, migration planned | [@appsec-lead] | YYYY-MM-DD |
| [EX-nnn] | [Rule] | [Location] | [Reason] | [Control] | [@name] | YYYY-MM-DD |

## 15. Training and review

<!-- Guidance: State how developers learn the standard and how compliance is measured. -->

New developers complete [secure coding training] within 30 days of
joining and yearly afterwards. Compliance is measured quarterly by
[open SAST findings per repository, exceptions past expiry, and the
share of repositories with all required tools]. The standard owner
reviews this document yearly and after any incident caused by a coding
weakness.

## Standard and licence note

Based on common industry practice for secure coding and on widely used
OWASP concepts, such as the risk areas of the OWASP Top 10 and the topic
areas of the OWASP Application Security Verification Standard. No OWASP
text is reproduced; the rules, IDs and code examples are original and
written for this collection. Consult OWASP resources (owasp.org) for
detailed guidance. Template licence: MIT.
