PhoenixmlDb
Resource Access
Queries and stylesheets can read only stored documents unless the operator allows local directories or HTTP origins
#Resource Access
Queries and stylesheets supplied by callers can read the documents stored in the database, and nothing else, until the operator allows specific directories or HTTP origins. This applies to the embedded engine, the gRPC server and the REST server.
PhoenixmlDb can't know what data goes into a database, or what an application built on it is meant to expose. So nothing outside the stored documents is reachable by default, and each inclusion is a deliberate, explicit choice by whoever runs the system. In the owner's words:
We have no idea what kind of data is going into these databases, and we're going to have to rely on customers having the foreknowledge that they're building a system to intentionally include/remove data for specific purposes.
The rules on this page hold under any policy made with ResourceAccessPolicy.Create and in both
servers. ResourceAccessPolicy.Unrestricted (embedded only) leaves resolution to the libraries,
except where noted.
#Allowing directories and origins (servers)
Both servers read the same settings, empty by default:
|
Setting |
Value |
|---|---|
|
|
Absolute paths of directories that exist. Files under them, at any depth, may be read. Nothing is writable. |
|
|
Origins, |
The same section holds six size and count limits (MaxModuleDepth, MaxModules,
MaxModuleBytes, MaxTotalModuleBytes, MaxTextResourceBytes, MaxTotalTextResourceBytes);
see XQuery Library Modules: Settings for their defaults and ranges.
As environment variables, index each entry:
export PhoenixmlDb__ResourceAccess__AllowedFileRoots__0=/srv/phoenixml/shared
export PhoenixmlDb__ResourceAccess__AllowedHttpOrigins__0=https://schemas.example.com
{
"PhoenixmlDb": {
"ResourceAccess": {
"AllowedFileRoots": [ "/srv/phoenixml/shared" ],
"AllowedHttpOrigins": [ "https://schemas.example.com" ]
}
}
}
-
Paths are canonicalised and match whole segments.
/data/adoesn't admit/data/ab. A..that climbs out of a root, or a symbolic link that points outside it, is denied. Roots containing spaces or non-ASCII characters work. -
The port is part of the origin. An origin without a port means the scheme's default port (443 for
https, 80 forhttp) only, sohttps://example.comdoesn't allowhttps://example.com:8443. -
HTTP fetches don't follow redirects. A redirect is a failed read, never a request to the redirect target.
-
Invalid values stop the server from starting, with a message naming the setting (for example
PhoenixmlDb:ResourceAccess:AllowedFileRoots:0). -
The effective allowlist is logged at startup.
-
The allowlist is configuration only. It can't be changed at runtime or through an API.
-
PhoenixmlDb:ResourceAccess:AllowStylesheetFileAccessis not a setting. A configuration that carries it, with any value, fails validation at startup.
#Embedded applications
DocumentDatabase.ResourceAccessPolicy (namespace PhoenixmlDb.Storage.Security) defaults to
ResourceAccessPolicy.DenyAll. To opt in:
using PhoenixmlDb.Storage.Security;
var options = new ResourceAccessOptions();
options.AllowedFileRoots.Add("/srv/phoenixml/shared");
options.AllowedHttpOrigins.Add("https://schemas.example.com");
db.ResourceAccessPolicy = ResourceAccessPolicy.Create(options);
Create throws ArgumentException listing every invalid setting; options.Validate() returns the same messages without throwing. Empty options give DenyAll.
Under ResourceAccessPolicy.Unrestricted, queries and stylesheets can read any file or URL the
process can. Use it only when every query and stylesheet is your own trusted code, never for
text that comes from users.
#What a query may read
|
Construct |
Reads |
|---|---|
|
|
Stored documents only, under every policy including |
|
|
Files inside |
|
|
Files inside |
|
|
Files inside |
|
External entities and DTDs in |
Files inside |
|
|
Not available: |
A module is held to the same rules as the query that brings it in.
Text functions:
-
A file larger than
MaxTextResourceBytesis not read:FOUT1170 … it is larger than N bytes (PhoenixmlDb:ResourceAccess:MaxTextResourceBytes).unparsed-text-availableanswersfalse. -
Past
MaxTotalTextResourceBytes:FOUT1170naming that setting. -
A missing file is
FOUT1170. -
With a requested encoding, an encoding that cannot be used, octets it cannot decode, and characters XML does not allow are
FOUT1190. With no requested encoding:FOUT1200.
fn:parse-xml:
-
Input with a document type declaration that does not parse is
FODC0006. -
Input that reads an external entity and nests elements more than 512 deep is
FODC0006.
#What a stylesheet may read (REST server)
This applies to POST /api/transform, /api/transform/raw and /api/transform/pipeline, and to
stylesheet registration, update and validation; to inline and registered stylesheets, and to every
module they import or include.
A stylesheet reads what a query may: files inside AllowedFileRoots and URLs on
AllowedHttpOrigins. The server reads each location and counts it against the
limits.
|
Construct |
Available |
|---|---|
|
|
Yes; file or http(s). Modules count against |
|
|
Yes; file or http(s). |
|
|
Yes. |
|
|
Yes; file or http(s). |
|
|
Yes; file or http(s). |
|
|
Yes (without |
-
One read per location per transformation, however often the stylesheet asks.
-
Redirects are not followed.
-
MaxTextResourceBytesandMaxTotalTextResourceBytesbound documents and text, over http(s) too. -
A module the stylesheet imports or includes is checked exactly like the stylesheet.
-
A registered stylesheet is checked again at every transformation, with its modules as they are then.
-
A stylesheet that reads a location outside the allowlist at run time registers, and is refused when it runs.
-
xsi:schemaLocationandxsi:noNamespaceSchemaLocationin a document a stylesheet validates are not read.
#Not available in stylesheets
These are refused before the stylesheet runs, with HTTP 400 and the detail
Stylesheet refused by the resource access policy (PhoenixmlDb:ResourceAccess): <reason>.
|
Construct |
Reason text |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A computed shadow attribute (value contains |
|
Also:
-
xsl:evaluateis disabled (XTDE3175). -
At run time
environment-variable()answers the empty sequence andavailable-environment-variables()an empty list. -
External entities are not read.
-
The check reads XSLT attributes whole. A string literal that contains a refused function name followed by
(or#is refused too. Build such text in an expression, for example{concat('trans', 'form(')}. -
Validation (
POST /api/transform/stylesheets/validate) reports the same text withisValid: false(HTTP200).
#Errors for a stylesheet read
A location that is not supplied (outside the allowlist, missing, unreadable, a link that leads out, or relative with no base) gets one answer per construct, naming the location as the stylesheet wrote it:
|
Construct |
Answer |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
Past a size limit |
|
|
A module that is not well-formed XML |
|
#How stylesheet modules read from files are named
Inside a module that was read from a file, static-base-uri(), base-uri(), resolve-uri('x')
and document-uri(document('')) give
phoenixmldb-file:///root-N/<path under the allowed root>
where N is the position of the root in PhoenixmlDb:ResourceAccess:AllowedFileRoots (0 for the
first). Example with root 0 = /srv/xsl:
|
Module named as |
|
|---|---|
|
|
|
|
included by another module as |
|
|
a module on an allowed origin |
its own URL |
|
the stylesheet sent in the request or registered |
empty (it has no base); |
-
Relative references inside a file module (
xsl:import href="../lib.xsl",doc('data.xml'),unparsed-text('notes/a.txt')) resolve under the same root and are read by the usual rules. -
A document or text read that way reports the rooted name as its
base-uri()/document-uri(). One read by afile:URI the stylesheet wrote keeps that URI. -
A reference that climbs out of its root names nothing:
XTSE0165/FODC0002/FOUT1170, andfalsefrom the availability functions. -
A stylesheet may write a rooted name itself (
phoenixmldb-file:///root-1/shared/lib.xsl). It names that file under that allowed root. -
file:/x(one slash),file://localhost/x, UNC paths, and schemes other thanfileand http(s) name no module (XTSE0165). Writefile:///…or a relative href. -
Inside a module,
doc('file:/abs/x.xml')andunparsed-text('file:/abs/x.txt')are answered like any location that is not available: HTTP400withFODC0002/FOUT1170.
#fn:transform
No nested transformation runs under a policy made with ResourceAccessPolicy.Create or under
DenyAll, in a query or in a stylesheet:
|
Where |
Answer |
|---|---|
|
A query (embedded, gRPC, REST), any call form |
|
|
A stylesheet on the REST server |
HTTP |
|
A stylesheet run by an embedded application with the policy object of |
|
|
|
|
|
|
The empty sequence |
The policy objects the database gives the XQuery and XSLT libraries have AllowTransformFunction
off. Under ResourceAccessPolicy.Unrestricted the database builds no policy object and
fn:transform is not restricted.
#Stylesheets in embedded applications
These rules hold for an application that runs a stylesheet with the database's stylesheet policy
(ResourceAccessPolicy.ToStylesheetResourcePolicy).
-
xsl:import-schemawith aschema-location. The schema document, and each document it includes or imports, is read once, insideAllowedFileRoots/AllowedHttpOrigins. A location that is not supplied isXTSE0165: Cannot find stylesheet module '<location>'. A relative location with no base isXTSE0165 … is relative and the stylesheet has no base URI to resolve it against. The REST server refuses this construct (Not available in stylesheets). -
Limits. Schema documents count, with the stylesheet modules, against
MaxModules,MaxModuleBytesandMaxTotalModuleBytes.MaxModuleDepthdoes not apply to them or to stylesheet modules. -
xsi:schemaLocationandxsi:noNamespaceSchemaLocationin a document a stylesheet validates are not read. -
The stylesheet resolver. The
ResourceResolverofResourceAccessPolicy.ToStylesheetResourcePolicyanswers a stylesheet module that is not supplied (not allowed, not there, not readable, past a module limit, or a relative reference with no base) withPhoenixmlDb.XQuery.Functions.XQueryException:XTSE0165in itsErrorCodeand the engine's text as its message. A document that is not supplied is answered the same way withFODC0002, and text withFOUT1170. A module past a module limit is refused each time it is asked for. A caller ofXsltTransformersees that exception as the error of the load. -
Implicit timezone. An application that runs its own
XsltTransformerwith this policy setsImplicitTimezoneitself, toDocumentDatabase.ImplicitTimezone. Without it the library takes the zone of the host. See Dates and Times.
#Relative references with no base
A relative location with no base URI names no location.
|
Construct |
Answer |
|---|---|
|
|
|
|
|
the construct's own code, same reason |
|
|
|
|
|
|
Use an absolute path or URI, declare base-uri in a query, or xml:base on the stylesheet.
A stylesheet with no xml:base can import or include a module by an absolute location.
#Modules read over HTTP
A transformation or a query that includes a module read from an http(s) location reads no files.
-
"Module" means a stylesheet module (
xsl:import,xsl:include) or an XQuery library module (import module … at,fn:load-xquery-module). A document or text read from http(s) as data is not a module. A schema document is not a module. -
"Reads no files" means every file location, whatever names it and in whatever spelling (
file:URI, path,phoenixmldb-file:///root-N/…, a relative reference under any base). -
One unit is one transformation (each pipeline step is its own) or one query (all its per-document evaluations and everything it loads).
-
http(s) locations on allowed origins keep working. Stored documents are not files.
-
With no http(s) module, nothing in this section applies.
-
A module that a
use-whenexcludes (on itsxsl:importorxsl:include, or on the root of the module that names it) is not read. An http(s) module excluded that way does not count. -
Under an
xml:base, a relativehrefnames the module under that base. Nothing else is read for it. -
ResourceAccessPolicy.Unrestrictedis not held to this rule.
A stylesheet or query that needs both a library from an http(s) origin and files can fetch the files over http(s) too, or keep the library as a file.
Each construct keeps its error code. The text ends with the sentence that names the rule:
|
Construct |
Code |
Text |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
— |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rules for content read from http(s):
-
A stylesheet module read from http(s) whose
xsl:import/xsl:includehrefnames a file is not loaded (XTSE0165). -
A stylesheet module read from http(s) declares an http(s) or relative
xml:baseonly. Otherwise:XTSE0165: Cannot load stylesheet module '<its URL>': its xml:base '…' is neither an http(s) URI nor relative. A stylesheet module read from an http(s) location declares no other base.Anxml:basethat begins with/or\on an XSLT element of such a module is refused too. -
A schema document read from http(s) refers to documents at http(s) locations only:
XQST0059 import schema location '…', referred to by the schema document at '…', is not read: a schema document read from an http(s) location refers to documents at http(s) locations only.A query that imports a schema from http(s) still reads files. -
Pipelines: the rule is per step. With
ContinueOnErrorthe step reports the error. -
With
MaxModules = N, a stylesheet that names more http(s) modules has N + 1 requested.
#Schema imports in queries
-
import schema … atreads the schema document and every document it names withxs:include,xs:import,xs:redefineandxs:override. Each is held to the allowlist. A refusal isXQST0059, naming the location as the schema document wrote it and "referred to by the schema document at '…'". -
A relative
schemaLocationis relative to where the referring document was read from, for files and over http(s). Documents that include each other load, each read once. -
xs:importwith a namespace and noschemaLocationloads nothing. -
A schema document with a document type declaration is
XQST0059. -
A schema document whose elements nest more than 512 deep is
XQST0059("nests elements more than 512 deep; such a document is not read as a schema"). -
Schema documents count toward
MaxModuleDepth,MaxModules,MaxModuleBytesandMaxTotalModuleBytes. -
xsi:schemaLocationwritten in an element given tovalidateloads nothing. -
Binding
xmlorxmlns, a prefix with an empty target namespace, and two imports of one namespace are refused.ErrorCodeisXPST0003; the message leads withXQST0070,XQST0057orXQST0058. -
import schemawritten inside a library module isXQST0009.
#Schema locations in the REST server validators
This applies to POST /api/v1/validate, /api/v1/validate/upload, /api/v1/validate/xml,
/api/v1/validate/svrl, /api/v1/validate/batch, /api/schemas/validate,
/api/schemas/{id}/validate, /api/schemas/validate-schema, schema registration, and write-time
validation.
A schema location is Schemas[].Uri in a validate request, the schemaLocation of xs:include,
xs:import and xs:redefine, and the href of sch:include. It is served when it is, in this
order:
-
a registered schema: the location is the name it was registered under;
-
a file in the schema directory: a relative name inside
PhoenixmlDb:Validation:BasePath. A name that leads out of the directory is not served from it; -
a location allowed by
PhoenixmlDb:ResourceAccess: an absolute path orfile:URI insideAllowedFileRoots, or an http(s) URL whose origin is inAllowedHttpOrigins.
The PhoenixmlDb:Validation settings are listed in
Server Configuration: Schema validation.
-
An http(s) location needs both
PhoenixmlDb:Validation:AllowHttpResolution: trueand its origin inPhoenixmlDb:ResourceAccess:AllowedHttpOrigins. -
Redirects are not followed.
-
A location is at most
PhoenixmlDb:ResourceAccess:MaxModuleBytes; larger is a400naming the setting. -
A relative
schemaLocationinside a served file or URL is relative to where that document was read from. -
sch:include hrefis a registered name, a schema-directory file, or an absolute allowed location. It is not relative to the including file. -
A schema brings in at most 256 locations.
-
An XSD read from an http(s) origin names http(s) locations only.
A location that is not served is HTTP 400: title: "Validation Error",
errorCode: "SCHEMA_LOCATION_NOT_AVAILABLE",
errors: [{ field: "schemaLocation", message, code: "SCHEMA_LOCATION_NOT_AVAILABLE" }], and the
detail:
Schema location '<as written>' is not available: it is not a registered schema, not a file in the schema directory (PhoenixmlDb:Validation:BasePath), and not a location allowed by PhoenixmlDb:ResourceAccess (an http(s) location also needs PhoenixmlDb:Validation:AllowHttpResolution).
The text is the same for every reason.
These are not schema locations. Nothing is read for them, with any settings:
-
a DOCTYPE in an XSD schema or in a document validated by XSD or Schematron: not accepted;
-
xsi:schemaLocation/xsi:noNamespaceSchemaLocationin the document: not followed; -
document()in a Schematron assertion or rule context: not available; -
sch:extends href, RELAX NGexternalRef/include: not implemented.
#Document nesting bounds
|
What |
Bound |
Answer past it |
|---|---|---|
|
A schema document ( |
512 elements deep |
|
|
|
512 |
|
|
A document validated by XSD, Schematron, RELAX NG or DTD on the REST server, including write-time validation |
1024 |
validation error: |
Neither bound is a setting.
#What error texts contain
-
An error names a location as the caller, or the importing module, wrote it.
-
For stylesheets, the text is the same whether the location is outside the allowlist, missing, a directory, or a link that leads out.
-
For queries, there is one text for every location outside the allowed directories, and another (
… was not found …) for a location inside an allowed directory with no file to read. -
REST transform errors name a file location only when the request wrote it: as an attribute value of the stylesheet, a string literal in one of its expressions, or a string parameter. Any other file location appears as
the requested location. Line and column are kept:[the requested location:1:126]. Relative names and http(s) URLs appear as written. -
The REST server's response doesn't include the denied content or a stack trace.
#Known limits
-
Inside a module, an
xml:basethat is a one-slashfile:/location (xml:base='file:/one/slash/'onxsl:import) or is not a URI (http://[not-a-host/, onxsl:importor on a literal result element) answers HTTP500, detailInvalid URI: …. The same in the stylesheet of the request is a400. Writefile:///. -
The two-argument
unparsed-text-available($uri, $encoding)answers from whether the resource is readable; it does not test the encoding. -
xsl:use-packageis not offered. -
In a stylesheet that turns on
expand-text, text containing a lone{or}is refused, even inside a part wherexsl:expand-text="no"turns it off again. Emit that text withxsl:textorxsl:value-of. -
Validators:
sch:extends hrefand RELAX NGexternalRef/includeare not implemented.
#Next Steps
-
XQuery Library Modules: module settings, resolution and errors
-
Server Mode: authentication and the other server settings