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.
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.
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")
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.
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")
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.