Skip to content

Conversation

@MilanKomsa
Copy link

No description provided.

}

// Validate against GS1 schema
const isValid = this.validateSchema(document);

Check failure

Code scanning / CodeQL

Resources exhaustion from deep object traversal High

Denial of service caused by processing
user input
with
allErrors: true
.

Copilot Autofix

AI 7 days ago

In general, the fix is to avoid using allErrors: true when validating user-controlled input in production. Instead, only enable allErrors conditionally (e.g., based on an environment variable or a debug flag) so production validates only until the first error and does not allocate unbounded error arrays.

The best fix here is to change the Ajv instantiation in EpcisValidationService to make allErrors conditional on an environment variable (for example EPCIS_DEBUG), mirroring the recommended pattern from the background section. This keeps current functionality for debugging (developers can still see all validation errors when they explicitly enable debug mode), but prevents the denial-of-service risk in normal operation. No other logic needs to change, because Ajv’s errors array is still populated when validation fails; it will just contain fewer entries in non-debug mode.

Concretely, in packages/plugin-epcis/src/services/EPCISValidationService.ts, update the constructor where new Ajv is called: replace allErrors: true with allErrors: process.env["EPCIS_DEBUG"] === "true". This is the only code change needed. No changes are required in packages/plugin-epcis/src/index.ts, and no new imports or helper methods are necessary.

Suggested changeset 1
packages/plugin-epcis/src/services/EPCISValidationService.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/plugin-epcis/src/services/EPCISValidationService.ts b/packages/plugin-epcis/src/services/EPCISValidationService.ts
--- a/packages/plugin-epcis/src/services/EPCISValidationService.ts
+++ b/packages/plugin-epcis/src/services/EPCISValidationService.ts
@@ -9,7 +9,7 @@
 
   constructor() {
     this.ajv = new Ajv({
-      allErrors: true,
+      allErrors: process.env["EPCIS_DEBUG"] === "true",
       strict: false,
       validateFormats: true,
     });
EOF
@@ -9,7 +9,7 @@

constructor() {
this.ajv = new Ajv({
allErrors: true,
allErrors: process.env["EPCIS_DEBUG"] === "true",
strict: false,
validateFormats: true,
});
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Contributor

@Lexpeartha Lexpeartha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also document new .env variable and add it to env.d.ts

@MilanKomsa
Copy link
Author

Please also document new .env variable and add it to env.d.ts
What new .env variable?

@MilanKomsa MilanKomsa requested a review from Lexpeartha January 16, 2026 14:43
@MilanKomsa MilanKomsa dismissed Lexpeartha’s stale review January 16, 2026 14:44

there is no .env variable used in code

Copy link

@Balki-OriginTrail Balki-OriginTrail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code so beautiful, it made my cry

} as any);
}
// Query publisher for asset status
const response = await fetch(`${publisherUrl}/api/dkg/assets/status/${encodeURIComponent(captureID)}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for the future, we should avoid calling our own server from that server. Instead we should expose the publisher plugin in context

Copy link
Contributor

@zsculac zsculac Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it would be wise to add some timeout here, so the request cannot hang forever.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do next week

console.error("[EPCIS Status] Error:", error);
res.status(500).json({
error: "Failed to get capture status",
message: error.message,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should return generic messages to the user and log the specific message on the server to avoid leaking sensitive data

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please elaborate

const { captureID } = req.params;
const publisherUrl = process.env.PUBLISHER_URL || "http://localhost:9200";

const captureIdPattern = /^[0-9]+$/;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a char limit on this id?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, should we have it?

): Promise<{ id: number; status: string; attemptCount: number }> {
const publisherUrl = process.env.PUBLISHER_URL || "http://localhost:9200";

const response = await fetch(`${publisherUrl}/api/dkg/assets`, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also no timeout. Also, why are the options hardcoded to "private" and "2". The user should be able to pass those values


// POST /epcis/capture - Accept EPCISDocument and queue for publishing
api.post(
"/epcis/capture",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could add a size limit or the file that is sent here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a middleware limit of 1gb, we can add some smaller limit if needed

}

// Send to publisher
const result = await sendToPublisher(document, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be some retries here and a fallback publishing using dkg.asset.create if the publisher plugin fails

Comment on lines +27 to +52
if (!document || typeof document !== "object") {
return {
valid: false,
errors: ["Document must be a valid JSON object"],
};
}

const doc = document as EPCISDocument;

// Check for required type
if (doc.type !== "EPCISDocument") {
return {
valid: false,
errors: [`Invalid type: expected "EPCISDocument", got "${doc.type}"`],
};
}

// Get event list from either location
const eventList = doc.eventList || doc.epcisBody?.eventList;

if (!eventList || !Array.isArray(eventList) || eventList.length === 0) {
return {
valid: false,
errors: ["EPCISDocument must contain at least one event in eventList or epcisBody.eventList"],
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we doing manual validation if ajv does that for us? Or is ajv not reliable enough?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

relict from previous iteration will remove it, also there was a possiblity to make epcis standard more loose so it accepts different forms as partners might require that depending on their business logic

}

// Get event list from either location
const eventList = doc.eventList || doc.epcisBody?.eventList;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the epcis json schema eventList can only be found in epcisBody.eventList, not directly in root. So if such a wrongfully formatted doc came throught this it would pass this check but fail on ajv check, which would be quite confusing.

Comment on lines +53 to +55
if (params.ual) {
return this.getEventByUal(params.ual);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if someone passes ual and some exclusive fields they are interested in they will still get everything back

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also we should do ual validation here and return a proper error. Currently if the ual is not valid it just returns prefixes.

You can check how this is done in dkg.js

Comment on lines +225 to +226
from: from as string,
to: to as string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be some validation of these vars before we pass them to the query builder

${filterClauses.join('\n ')}
}
ORDER BY DESC(?eventTime)
LIMIT 100`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the limit hardcoded? This should not be up to us to decide


res.json({
success: true,
query: sparqlQuery,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we returning the query?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not? i find it nicely informative

// Namespace prefixes for EPCIS queries
const PREFIXES = `
PREFIX epcis: <https://gs1.github.io/EPCIS/>
PREFIX kam: <https://kam.example.com/epcis/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does kam need to be here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be removed


// BizLocation filter
if (params.bizLocation) {
wherePatterns.push(`?event epcis:bizLocation "${escapeSparql(params.bizLocation)}" .`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked in the epcis json schema and it seems like the bizLocation expects the ID property, so it should probably be params.bizLocation.id

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be changed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants