1// Package nestedpkg provides helpers for package-path based access control.2// It is useful for upgrade patterns relying on namespaces.3//4// SECURITY: every exported helper takes `rlm realm` and reads both5// `rlm.PkgPath()` and `rlm.Previous().PkgPath()` to make an6// authorization decision. To close Class-2 designation forgery (a7// hostile realm stashes a captured realm value and passes it back to8// spoof identity), every helper gates on `rlm.IsCurrent()` first. The9// Is* predicates return false on stale rlm (fail-closed); the Assert*10// helpers panic. See docs/resources/gno-security.md.11package nestedpkg1213import "strings"1415// IsCallerSubPath checks if the caller realm is located in a subfolder of the current realm.16func IsCallerSubPath(_ int, rlm realm) bool {17 if !rlm.IsCurrent() {18 return false19 }20 var (21 curPath = rlm.PkgPath() + "/"22 prevPath = rlm.Previous().PkgPath() + "/"23 )24 return strings.HasPrefix(prevPath, curPath)25}2627// AssertCallerIsSubPath panics if IsCallerSubPath returns false.28func AssertCallerIsSubPath(_ int, rlm realm) {29 if !rlm.IsCurrent() {30 panic("unauthorized: rlm is not the caller's live cur")31 }32 var (33 curPath = rlm.PkgPath() + "/"34 prevPath = rlm.Previous().PkgPath() + "/"35 )36 if !strings.HasPrefix(prevPath, curPath) {37 panic("call restricted to nested packages. current realm is " + curPath + ", previous realm is " + prevPath)38 }39}4041// IsCallerParentPath checks if the caller realm is located in a parent location of the current realm.42func IsCallerParentPath(_ int, rlm realm) bool {43 if !rlm.IsCurrent() {44 return false45 }46 var (47 curPath = rlm.PkgPath() + "/"48 prevPath = rlm.Previous().PkgPath() + "/"49 )50 return strings.HasPrefix(curPath, prevPath)51}5253// AssertCallerIsParentPath panics if IsCallerParentPath returns false.54func AssertCallerIsParentPath(_ int, rlm realm) {55 if !rlm.IsCurrent() {56 panic("unauthorized: rlm is not the caller's live cur")57 }58 var (59 curPath = rlm.PkgPath() + "/"60 prevPath = rlm.Previous().PkgPath() + "/"61 )62 if !strings.HasPrefix(curPath, prevPath) {63 panic("call restricted to parent packages. current realm is " + curPath + ", previous realm is " + prevPath)64 }65}6667// IsSameNamespace checks if the caller realm and the current realm are in the same namespace.68func IsSameNamespace(_ int, rlm realm) bool {69 if !rlm.IsCurrent() {70 return false71 }72 var (73 curNs = nsFromPath(rlm.PkgPath()) + "/"74 prevNs = nsFromPath(rlm.Previous().PkgPath()) + "/"75 )76 return curNs == prevNs77}7879// AssertIsSameNamespace panics if IsSameNamespace returns false.80func AssertIsSameNamespace(_ int, rlm realm) {81 if !rlm.IsCurrent() {82 panic("unauthorized: rlm is not the caller's live cur")83 }84 var (85 curNs = nsFromPath(rlm.PkgPath()) + "/"86 prevNs = nsFromPath(rlm.Previous().PkgPath()) + "/"87 )88 if curNs != prevNs {89 panic("call restricted to packages from the same namespace. current realm is " + curNs + ", previous realm is " + prevNs)90 }91}9293// nsFromPath extracts the namespace from a package path.94func nsFromPath(pkgpath string) string {95 parts := strings.Split(pkgpath, "/")9697 // Specifically for gno.land, potential paths are in the form of DOMAIN/r/NAMESPACE/...98 // XXX: Consider extra checks.99 // XXX: Support non gno.land domains, where p/ and r/ won't be enforced.100 if len(parts) >= 3 {101 return parts[2]102 }103 return ""104}105106// XXX: Consider adding IsCallerDirectlySubPath107// XXX: Consider adding IsCallerDirectlyParentPath108Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.