Join our Newsletter — 33% off our NHI Course

What is the difference between cleaning a path and canonicalising it in Go?

Cleaning a path removes relative components such as ../ and produces a simpler string representation. Canonicalising goes further by resolving the path on disk, including symbolic links, so you can see where the request really lands. Both steps matter, but only canonicalisation helps detect cases where a harmless looking path actually points to a sensitive file.

Why This Matters for Security Teams

In Go, the distinction is not cosmetic, it affects whether a path is merely tidied or actually grounded in the filesystem state. path.Clean rewrites the string by removing ., .. and duplicate separators, while canonicalisation resolves what that path means on disk. For security review, that difference determines whether a check is operating on user input or on the real target after symbolic links and mount boundaries are taken into account.

That matters anywhere a request path is compared against an allowlist, document root, upload directory, or package boundary. A clean-looking path can still traverse through a symlink into a sensitive location, so relying on string normalisation alone can create a false sense of safety. OWASP API Security Top 10 is useful here because path handling issues often surface as broken object access or unsafe file access in APIs.

In practice, teams usually find the mistake only after a file access check has already been built around the wrong assumption about what the path resolves to.

How It Works in Practice

path.Clean and filepath.Clean are string-level operations. They make the path simpler, but they do not consult the filesystem. That means they cannot tell you whether /app/data/report.txt is a normal file, a symlink, or a path that will eventually land somewhere else because of the runtime environment.

Canonicalisation is different because it resolves the path against the filesystem. In Go, that typically means using filepath.EvalSymlinks and then checking the resulting absolute path before allowing access. The security value is that the resolved location, not the original string, becomes the basis for comparison. This is the only reliable way to confirm that a requested file stays inside the intended boundary.

  • Use cleaning when you only need a stable, readable path string for display or basic manipulation.
  • Use canonicalisation when the path will be compared against a protected directory, a policy boundary, or a trust decision.
  • Compare the resolved path after canonicalisation, not the user-supplied input before it.
  • Account for symlinks, mounted volumes, and any directory that a lower-privileged actor can influence.

The practical limitation is that canonicalisation can fail or change behaviour in environments where the target path does not yet exist, where components are created dynamically, or where symlinks are expected as part of the deployment model.

Common Variations and Edge Cases

Tighter path handling often increases operational friction, because legitimate workflows sometimes depend on symlinks, generated paths, or directories that appear only at runtime. The right choice is therefore not “always canonicalise”, but “canonicalise when a security boundary depends on the final location”.

One common edge case is validation before file creation. If the target does not exist yet, canonicalisation may not be possible all the way to the leaf path, so teams need to resolve and trust the nearest existing parent and then enforce where new files may be created. Another edge case is cross-platform behaviour, because Windows and Unix path semantics differ enough that a check that looks safe on one system may be incomplete on another.

NIST SP 800-53 Rev 5 Security and Privacy Controls is a good alignment point for this kind of control because file access protection, configuration integrity, and boundary enforcement are all part of the control set. When the environment allows user-controlled symlinks or shared writable directories, path cleaning alone is especially weak because the effective destination can change after validation.

Risk and Threat Considerations

The main risk is path traversal and boundary bypass, especially when a security check trusts a cleaned string instead of the resolved filesystem target. That creates exposure when an attacker can influence symlinks, mounted paths, or directory structure and steer an apparently safe request into a sensitive location.

Failure mechanism: the application validates the user input before resolution, then opens or compares the file later against a different real path. If a symlink or directory substitution changes the destination in between, the control is evaluating the wrong object.

Impact: unauthorized read, overwrite, or execution against files outside the intended scope, plus broken assumptions in audit logs and access control decisions.

Standards & Framework Alignment

This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.

NIST CSF 2.0 and CIS Controls v8 set the governance and control requirements practitioners need to meet.

Framework Control / Reference Relevance
NIST CSF 2.0 PR.AC-4 — Access Permissions and Authorizations Are Managed Resolved path checks support correct authorization decisions.
Recommendation — Verify access against the resolved path before allowing file operations.
CIS Controls v8 6.3 — Access Control Management File boundaries require enforced access control on the real target.
Recommendation — Restrict file access based on resolved locations, not user input strings.

Practitioner Guidance

What to prioritise: Treat canonicalisation as a control, not a formatting step, whenever path equality or directory containment affects access. If the code is only cleaning paths for display, that is fine; if the code is making a trust decision, the resolved path must be checked.

Decision rule: If a user-controlled path can reach a privileged file operation, validate the resolved filesystem target and reject paths that escape the allowed root. If the target path may not exist yet, apply the same rule to the deepest existing parent and control where creation can occur.

What to verify: Confirm that the implementation handles symlinks, does not rely on string prefix checks alone, and is tested on the same operating systems and filesystems used in production. The common mistake is assuming Clean and canonicalisation are interchangeable because they often produce similar-looking output.

Practitioner takeaway: The safe pattern is to clean for readability, canonicalise for trust, and never let a path-checking control make a security decision on a string that has not been resolved.