Course outline

Secure Software Development / Intermediate: implementation boundaries and verification / Lesson 9

Constrain files and deserialization capabilities

Treat uploaded metadata, storage names, parsing, and retrieval as separate defensive boundaries.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain why a file extension or decoded object does not establish safety.
  • Prefer bounded data formats and server-owned storage identities.

Review the complete file lifecycle

A workshop attachment crosses upload, validation, storage, processing, and retrieval boundaries. A filename and declared media type are claims, not proof of content or permission. Define the business need before allowing formats: a public schedule document and a private registration import have different handling requirements. Consider size, count, parser complexity, storage location, access, retention, and cleanup. A file accepted for storage is not automatically safe to render inline or execute. Keep examples as metadata and local data parsing; this lesson never uploads a file, opens an untrusted document, or invokes a conversion tool.

Separate display names from storage authority

Let an approved server-side mechanism assign the storage identity rather than treating a client filename as a path. The display name can remain bounded metadata under a separate output policy. The first program constructs a storage label from a fixed classroom id and an allowlisted kind. It performs no filesystem operation and is not a path-containment validator. Real storage must enforce its own namespace, access, and atomicity rules. Avoid relying only on removing a few path characters; platform separators, normalization, links, and archive extraction can create additional interpretation paths.

python A narrow storage-label policy without file access
extensions = {"schedule-json": ".json", "public-text": ".txt"}
def storage_label(server_id, kind):
    if server_id != "upload-lab-7" or kind not in extensions:
        return "INVALID_STORAGE_REQUEST"
    return server_id + extensions[kind]

assert storage_label("upload-lab-7", "schedule-json") == "upload-lab-7.json"
assert storage_label("client-path", "schedule-json") == "INVALID_STORAGE_REQUEST"
assert storage_label("upload-lab-7", "other") == "INVALID_STORAGE_REQUEST"
print("server-owned storage labels checked")
Expected output
server-owned storage labels checked

The complete program checks three synthetic metadata cases. The id check is a classroom fixture, not a production identifier generator. It neither creates paths nor proves containment, file type, permission, or safe content.

Decode data without restoring executable objects

A general object deserializer may restore types or invoke behavior that a simple data interchange format does not need. Prefer a reviewed data format and explicitly validate its decoded structure. JSON parsing still needs resource bounds and domain validation; it is not a universal safety guarantee. The second program parses a tiny known JSON schedule and selects only documented fields after exact shape checks. It does not evaluate expressions or instantiate arbitrary classes. Actual byte limits, nesting limits, duplicate-key policy, and framework parser behavior require separate review in the selected environment.

python Parse and validate a tiny known data object
import json
raw = '{"title":"Paper Gardens","minutes":45}'
value = json.loads(raw)
assert type(value) is dict
assert set(value) == {"title", "minutes"}
assert isinstance(value["title"], str) and 1 <= len(value["title"]) <= 60
assert type(value["minutes"]) is int and 1 <= value["minutes"] <= 180
accepted = {"title": value["title"], "minutes": value["minutes"]}
assert accepted["minutes"] == 45
print("bounded schedule data checked")
Expected output
bounded schedule data checked

The complete program parses a fixed local string and checks selected structure and bounds. Assertions are teaching checks, not a production error-handling API. No file, object deserializer, network, or external parser package is used.

Authorize retrieval and control downstream interpretation

A private import result must remain private when downloaded later, and a public attachment must use a reviewed serving policy. Recheck the subject-resource relationship at retrieval rather than treating knowledge of a storage name as permission. Rendering, spreadsheet opening, archive extraction, and image conversion each add a destination interpreter and potential resource cost. Keep content outside executable locations and use platform-supported isolation where required by the approved architecture. These are design requirements here; no actual storage or serving configuration is changed, and no claim is made that an antivirus or content processor was run.

Test each layer without overclaiming

Use benign fixtures for accepted formats, unsupported kinds, invalid metadata, missing permission, and expired results. Verify unchanged protected state on refusal and cleanup of artifacts the test actually owns. If a parser or processor is not available, keep its example illustrative and record the missing boundary instead of installing a complex stack to obtain a misleading green result. A safe storage label, a valid JSON object, and a permitted download are three different claims. Verification should preserve those distinctions and connect them to the complete file lifecycle.

Common errors and fixes

An extension is accepted as content proof

Why it happens
Client metadata is trusted beyond its meaning.
How to fix it
Apply the approved type, content, size, and processing policy.
Prevent it next time
Review upload, processing, storage, and retrieval separately.

A decoded object is used without shape checks

Why it happens
Parsing success is mistaken for domain validity.
How to fix it
Validate documented fields, types, bounds, and relationships.
Prevent it next time
Keep parser and domain failure cases distinct.

A private file is available to anyone knowing its label

Why it happens
Storage identity is confused with authorization.
How to fix it
Enforce retrieval permission and lifecycle rules.
Prevent it next time
Include later-download and reassignment cases.

Check your understanding

1. Why is server-owned naming insufficient for a private schedule import?

Show worked solution

It reduces client control over storage identity but does not validate content, constrain parser resources, or authorize retrieval. Define each boundary separately and verify accepted/refused fixtures at the appropriate layer. Do not treat an opaque name as the sole confidentiality control.

2. A JSON schedule parses but minutes is a string. What should happen?

Show worked solution

Under the declared integer contract, reject it before creating the protected schedule. Avoid silently coercing unless the contract explicitly permits that representation. Check that the refusal creates no result artifact and report a stable nonsecret validation category.

Key takeaways

  • Files cross several independent trust boundaries.
  • Server-owned identity does not establish content or retrieval permission.
  • Data parsing needs explicit structure and resource policy.

Official references