fileblock

package
v1.0.126 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package fileblock is the file-type blocking engine: an extension/MIME/ Content-Disposition blocklist (FileBlocker) and named file-type profiles (FileProfileStore, in fileprofile.go). It depends only on the shared seam (internal/obs for logging, internal/fileutil for atomic persistence) and the standard library — no dependency on the rest of Culvert (ADR-0002 leaf, unblocked by the ADR-0003 seam).

Index

Constants

This section is empty.

Variables

View Source
var DefaultBlockedExts = []string{
	".exe", ".dll", ".bat", ".cmd", ".ps1",
	".vbs", ".scr", ".msi", ".pif", ".com",
}

DefaultBlockedExts is loaded at startup when no config override is provided. Covers common Windows malware/script delivery formats.

Functions

func BlockConn

func BlockConn(dst interface {
	Write([]byte) (int, error)
	Close() error
}, host, urlPath, ext, source string)

BlockConn writes a synthetic HTTP/1.1 403 response to a raw connection and closes it. Retained for the raw-conn callers and its own test; the SSL-inspect path no longer uses it (it emits via the protocol-neutral blockResponder so the same detector serves HTTP/2, where closing the shared conn on a per-stream block would kill sibling streams and Connection: close is illegal per RFC 9113 §8.2.2). The force-close here prevents HTTP/1.1 pipelined-request bypass.

func BlockMessage added in v1.0.70

func BlockMessage(ext, source string) string

BlockMessage returns the plain-text body of a file-block 403 for the given extension and source label. Exposed so the SSL-inspect path can emit the block through its protocol-neutral responder (HTTP/1.1 or HTTP/2) without this package owning HTTP framing; the legacy raw-conn BlockConn uses it too.

func ExtractCDFilename

func ExtractCDFilename(cd string) string

ExtractCDFilename extracts the filename from a Content-Disposition header. Returns "" if no filename is found. Used by per-rule file profile checking when the download URL doesn't contain the file extension (e.g. SourceForge's /files/latest/download pattern).

func LogBlock added in v1.0.70

func LogBlock(host, urlPath, ext, source string)

LogBlock emits the FILE_BLOCKED tunnel observability line. Split out of BlockConn so the SSL-inspect path can log identically while emitting the wire block through its own responder (keeping this package framing-free).

Types

type FileBlocker

type FileBlocker struct {
	// contains filtered or unexported fields
}

FileBlocker holds the set of file extensions to block. Extensions are normalised to lowercase with a leading dot (e.g. ".exe"). All operations are safe for concurrent use. Persists to a JSON file when a path is configured via SetPath().

func NewBlocker

func NewBlocker() *FileBlocker

NewBlocker returns an empty, ready-to-use FileBlocker. package main holds the process-wide singleton (var fileBlocker = fileblock.NewBlocker()).

func (*FileBlocker) Add

func (fb *FileBlocker) Add(ext string)

Add inserts a normalised extension into the block list and persists.

func (*FileBlocker) CheckContentDisposition

func (fb *FileBlocker) CheckContentDisposition(cd string) string

CheckContentDisposition returns the blocked extension if the Content-Disposition response header carries a filename with a blocked extension (catches downloads that use a generic URL but declare the real file name in the header).

func (*FileBlocker) CheckContentType

func (fb *FileBlocker) CheckContentType(contentType string) string

CheckContentType returns the blocked extension if the response Content-Type header matches a dangerous MIME type whose associated extension is in the block list. This prevents bypass by renaming files (e.g. malware.exe → malware.txt). The contentType parameter should be the raw Content-Type header value (e.g. "application/x-msdownload; charset=utf-8").

func (*FileBlocker) CheckExt

func (fb *FileBlocker) CheckExt(ext string) string

CheckExt checks if a specific extension is in the block list. Extension should include the dot (e.g., ".exe").

func (*FileBlocker) CheckPath

func (fb *FileBlocker) CheckPath(urlPath string) string

CheckPath returns the blocked extension if urlPath ends with a blocked file extension, or empty string if the request is allowed. Pass r.URL.Path (not the full URL) to avoid matching query-string artefacts.

func (*FileBlocker) ClearAll

func (fb *FileBlocker) ClearAll()

ClearAll removes all blocked extensions. Used by config import "replace" mode.

func (*FileBlocker) Count

func (fb *FileBlocker) Count() int

Count returns the number of blocked extensions.

func (*FileBlocker) List

func (fb *FileBlocker) List() []string

List returns a snapshot of the blocked extensions.

func (*FileBlocker) Remove

func (fb *FileBlocker) Remove(ext string)

Remove deletes an extension from the block list and persists.

func (*FileBlocker) ReplaceAll

func (fb *FileBlocker) ReplaceAll(exts []string)

ReplaceAll atomically replaces the in-memory extension set with the normalised contents of exts and persists once. Per-element semantics mirror Add exactly: lowercase + trim, leading-dot inserted if missing, empty / bare-dot entries skipped, duplicates collapsed by the set.

CL-13: used by applyConfigSnapshot's FileBlockExtensions branch to replace the prior ClearAll + per-extension Add loop, which triggered N+1 atomicWriteFile syscalls per snapshot apply (cap 10_000 extensions per maxSnapFileBlockExtensions). ReplaceAll triggers exactly one save() call regardless of N.

Save is invoked unconditionally — including when the new set is equal to the existing set. Skipping save on equal content would require deep-comparing maps under the lock and add a fast-path that could deviate from the long-term durability guarantee, which is out of CL-13 scope per the user brief.

func (*FileBlocker) SetPath

func (fb *FileBlocker) SetPath(p string)

SetPath configures the persistence file and loads any previously saved extensions from it. If the file doesn't exist, the current in-memory state is kept (caller should load defaults before calling SetPath).

type FileExtProfile

type FileExtProfile struct {
	ID         string   `json:"id"`
	Name       string   `json:"name"`
	Extensions []string `json:"extensions"`
}

FileExtProfile is a named set of file extensions used for per-policy-rule blocking.

type FileProfileStore

type FileProfileStore struct {
	// contains filtered or unexported fields
}

FileProfileStore manages a persistent collection of file extension profiles. All operations are safe for concurrent use.

func (*FileProfileStore) Create

func (s *FileProfileStore) Create(name string, exts []string) (*FileExtProfile, error)

Create adds a new profile. Returns an error if the name is already taken.

func (*FileProfileStore) Delete

func (s *FileProfileStore) Delete(id string) error

Delete removes a profile by ID.

func (*FileProfileStore) GetByID

func (s *FileProfileStore) GetByID(id string) *FileExtProfile

GetByID returns the profile with the given ID, or nil.

func (*FileProfileStore) GetByName

func (s *FileProfileStore) GetByName(name string) *FileExtProfile

GetByName returns the profile with the given name (case-insensitive), or nil.

func (*FileProfileStore) List

func (s *FileProfileStore) List() []*FileExtProfile

List returns a copy of all profiles.

func (*FileProfileStore) Load

func (s *FileProfileStore) Load(path string) error

Load reads profiles from disk. If the file does not exist the built-in profiles are seeded and persisted so policy rules continue to work.

func (*FileProfileStore) NameByID added in v1.0.72

func (s *FileProfileStore) NameByID(id string) (string, bool)

NameByID returns the profile's name (a value copy, read under the lock) and whether it exists. Callers needing only the name must use this rather than GetByID().Name — GetByID returns the LIVE pointer, and reading .Name off it outside the lock races a concurrent Update (which mutates p.Name in place).

func (*FileProfileStore) ReplaceAll

func (s *FileProfileStore) ReplaceAll(profiles []FileExtProfile)

ReplaceAll atomically replaces all profiles in memory and persists the new set (used by cluster config sync). Disk failure is logged; the in-memory swap is authoritative — the next CP heartbeat will re-attempt.

func (*FileProfileStore) Save

func (s *FileProfileStore) Save() error

Save persists the current profile set to the configured path.

func (*FileProfileStore) SetPath

func (s *FileProfileStore) SetPath(p string)

SetPath sets the persistence file path without reading from disk (use Load to also read existing profiles). Added for the package boundary: package main integration tests need to redirect persistence without the whitebox field access they relied on before the move.

func (*FileProfileStore) Update

func (s *FileProfileStore) Update(id, name string, exts []string) error

Update replaces the name and/or extensions of an existing profile.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL