Install
$ agentstack add skill-adobe-skills-asset-manager ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Asset Manager API — AEM as a Cloud Service
Overview
com.day.cq.dam.api.AssetManager is not removed on AEM CS — but several of its operations are. The binary-path APIs that relied on direct filesystem access (createAssetForBinary, getAssetForBinary, removeAssetForBinary) do not exist on CS because the cloud runtime does not expose a filesystem path to the AEM JVM. Binary I/O for client uploads moves to Direct Binary Access — bytes flow directly between the client and Adobe's binary store, bypassing the JVM entirely.
API status on AEMaaCS:
| API | Status | What to use instead | |-----|--------|---------------------| | AssetManager.getAsset(path) | ✅ Supported | No change — still works for reads | | AssetManager.createAsset(path, InputStream, mimeType, doSave) | ⚠️ Strongly discouraged for client-facing uploads (2 GB binary limit, blocks the JVM, asset-processing pipeline expects Direct Binary Access). Still callable for in-JVM utilities where the binary is small and already in the JVM (bundled resources, fixtures, back-office imports). | Direct Binary Access for client uploads (@adobe/aem-upload JavaScript SDK). In-JVM small-file creation OK. | | AssetManager.createAssetForBinary(binaryFilePath, doSave) | ❌ Removed — relied on filesystem path the cloud runtime does not expose | Direct Binary Access | | AssetManager.getAssetForBinary(binaryFilePath) | ❌ Removed | resolver.getResource(repoPath).adaptTo(Asset.class) — look up by repository path, not binary path | | AssetManager.removeAssetForBinary(binaryFilePath, doSave) | ❌ Removed | In-JVM: resolver.delete(resource) + resolver.commit(). External: HTTP Assets API DELETE /api/assets{path} |
Three CS-specific principles:
| Principle | Why | |-----------|-----| | Binary I/O for client uploads goes Direct Binary Access — never through the JVM | The cloud runtime does not have a JVM-accessible filesystem; bytes through the JVM are slow, memory-bound, and blocked by a 2 GB ceiling | | In-JVM delete uses resolver.delete() + resolver.commit() with a service user — never AEM calling its own HTTP API | Self-looping HTTP adds latency, requires credentials you shouldn't store, and breaks idempotency | | External callers authenticate with IMS / dev-console bearer tokens — never hardcoded passwords or userId:password strings | AEMaaCS has no admin user with a static password; IMS service credentials are the only supported external auth path |
Classification — choose before making any changes
Uses AssetManager.createAssetForBinary(...) or AssetManager.getAssetForBinary(...) (removed APIs) → Apply Path A (C1–C3).
Uses AssetManager.createAsset(path, InputStream, mimeType, doSave) in a client-facing servlet (accepts upload from a browser or external caller) → Apply Path A — migrate to Direct Binary Access (C2 client-facing branch).
Uses AssetManager.removeAssetForBinary(...) (removed API) → Apply Path B (D1–D3).
Uses both a removed create API AND a removed delete API → Apply Path A first, then Path B. Both transformations are independent.
Uses AssetManager.getAsset(path) only (read access) → Already compliant — no migration needed.
Uses AssetManager.createAsset(path, InputStream, mimeType, doSave) in an in-JVM back-office utility (scheduled import, test fixture, asset post-processing — binary is already in the JVM and small) → Already supported — verify the surrounding code uses a service-user resolver and closes the stream in try-with-resources, then skip.
One pattern per session. If the file has multiple flows, migrate one direction (create OR delete) at a time.
Before starting: Read [../references/aem-cloud-service-pattern-prerequisites.md](../references/aem-cloud-service-pattern-prerequisites.md) and apply SCR→DS, service-user, and SLF4J fixes if present in the same changeset.
Complete example — Path A (create / upload)
Before (client-facing upload via AssetManager.createAsset)
package com.example.servlets;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.AssetManager;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.InputStream;
@Component(immediate = true, metatype = false)
public class CreateAssetServlet extends SlingAllMethodsServlet {
@Reference
private AssetManager assetManager;
@Override
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
String assetPath = request.getParameter("path");
String mimeType = request.getParameter("mimeType");
InputStream inputStream = request.getInputStream();
try {
Asset asset = assetManager.createAsset(assetPath, inputStream, mimeType, true);
response.setContentType("text/plain");
response.getWriter().write("Asset created: " + asset.getPath());
} catch (Exception e) {
System.err.println("Error creating asset: " + e.getMessage());
e.printStackTrace();
response.setStatus(500);
response.getWriter().write("Error: " + e.getMessage());
}
}
}
After — Cloud Service compatible (client-side Direct Binary Access)
On AEMaaCS the upload happens directly between the client and the binary store; the AEM JVM orchestrates the upload but does not carry the bytes. Delete the upload-accepting servlet entirely, and replace it with a client-side call to the Direct Binary Access HTTP API via @adobe/aem-upload:
import DirectBinary from '@adobe/aem-upload';
/**
* Uploads a file to AEMaaCS via Direct Binary Access.
* @param {File|Blob} file The binary to upload
* @param {string} assetPath Repository destination, e.g. "/content/dam/my-site/file.pdf"
* @param {string} host AEM author host, e.g. "https://author-p123-e456.adobeaemcloud.com"
* @param {string} token IMS bearer token (Adobe Developer Console service credentials,
* or short-lived user token from the AEM login flow)
*/
async function uploadAsset(file, assetPath, host, token) {
const upload = new DirectBinary.DirectBinaryUpload();
const options = new DirectBinary.DirectBinaryUploadOptions()
.withUrl(`${host}/api/assets${assetPath}`)
.withUploadFiles([{
fileName: file.name,
blob: file,
fileSize: file.size
}])
.withHttpOptions({
headers: {
Authorization: `Bearer ${token}` // IMS / dev-console token — never a static password
}
});
return upload.uploadFiles(options);
}
If the servlet path must remain (routing, ACL, audit reasons), convert it to return 410 Gone with a documented replacement — do not silently call a removed API:
package com.example.servlets;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.ServletResolverConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
@Component(service = Servlet.class, property = {
ServletResolverConstants.SLING_SERVLET_PATHS + "=/bin/createasset"
})
public class CreateAssetServlet extends SlingAllMethodsServlet {
private static final Logger LOG = LoggerFactory.getLogger(CreateAssetServlet.class);
@Override
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
LOG.warn("Legacy upload endpoint hit — clients must use Direct Binary Access at /api/assets");
response.setStatus(410); // 410 Gone — the operation moved
response.setContentType("application/json");
response.getWriter().write(
"{\"error\":\"Use Direct Binary Access (POST /api/assets + @adobe/aem-upload) to upload assets.\"}");
}
}
Complete example — Path B (delete)
Before (removed removeAssetForBinary API)
package com.example.servlets;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import com.day.cq.dam.api.AssetManager;
import javax.servlet.ServletException;
import java.io.IOException;
@Component(immediate = true, metatype = false)
public class DeleteAssetServlet extends SlingAllMethodsServlet {
@Reference
private AssetManager assetManager;
@Override
protected void doDelete(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
String binaryFilePath = request.getParameter("path");
try {
boolean isDeleted = assetManager.removeAssetForBinary(binaryFilePath, true);
response.setContentType("text/plain");
if (isDeleted) {
response.getWriter().write("Asset deleted: " + binaryFilePath);
} else {
response.setStatus(404);
response.getWriter().write("Asset not found: " + binaryFilePath);
}
} catch (Exception e) {
System.err.println("Error deleting asset: " + e.getMessage());
e.printStackTrace();
response.setStatus(500);
response.getWriter().write("Error: " + e.getMessage());
}
}
}
After — in-JVM delete with a service-user resolver (recommended for code already inside AEM)
Server-side code that already has a trusted ResourceResolver should delete assets directly via the resource API. No HTTP, no credentials, no self-loopback through AEM's own API:
package com.example.servlets;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.servlets.ServletResolverConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Collections;
@Component(service = Servlet.class, property = {
ServletResolverConstants.SLING_SERVLET_PATHS + "=/bin/deleteasset"
})
public class DeleteAssetServlet extends SlingAllMethodsServlet {
private static final Logger LOG = LoggerFactory.getLogger(DeleteAssetServlet.class);
private static final String SUBSERVICE = "asset-admin-service";
@Reference
private ResourceResolverFactory resolverFactory;
@Override
protected void doDelete(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
String assetPath = request.getParameter("path");
if (assetPath == null || assetPath.isEmpty() || !assetPath.startsWith("/content/dam/")) {
response.setStatus(400);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"valid /content/dam path parameter required\"}");
return;
}
try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(
Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, SUBSERVICE))) {
Resource resource = resolver.getResource(assetPath);
if (resource == null) {
response.setStatus(404);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"asset not found\"}");
return;
}
resolver.delete(resource);
resolver.commit();
response.setContentType("application/json");
response.getWriter().write("{\"success\":true}");
LOG.info("Deleted asset at {}", assetPath);
} catch (LoginException e) {
LOG.error("Could not open service resolver for subservice '{}'", SUBSERVICE, e);
response.setStatus(500);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"internal error\"}");
} catch (PersistenceException e) {
LOG.error("Commit failed while deleting asset {}", assetPath, e);
response.setStatus(500);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"internal error\"}");
}
}
}
After — external-caller delete via the HTTP Assets API
If the deleter is outside the AEM JVM (integration backend, CLI, worker), call the HTTP API with an IMS or dev-console bearer token — never a hardcoded password:
import axios from 'axios';
/**
* Deletes an asset from AEMaaCS via the HTTP Assets API.
* Caller must provide a bearer token from Adobe Developer Console service credentials
* or a short-lived user token from the AEM login flow.
*/
export async function deleteAsset({ host, assetPath, bearerToken }) {
const response = await axios.delete(`${host}/api/assets${assetPath}`, {
headers: { Authorization: `Bearer ${bearerToken}` },
validateStatus: status => status === 200 || status === 204 || status === 404
});
return response.status !== 404;
}
C1 — Replace createAssetForBinary / getAssetForBinary
These APIs do not exist on AEMaaCS. Remove every call.
// BEFORE (removed APIs)
assetManager.createAssetForBinary(binaryFilePath, doSave);
Asset asset = assetManager.getAssetForBinary(binaryFilePath);
Replacements:
| Legacy call | AEMaaCS replacement | |-------------|---------------------| | createAssetForBinary(binaryFilePath, doSave) | Direct Binary Access via @adobe/aem-upload or HTTP POST /api/assets. The binary never sits on the AEM filesystem. | | getAssetForBinary(binaryFilePath) | resolver.getResource(repoPath).adaptTo(Asset.class) using the repository path (not a filesystem path) |
// Direct Binary Access — client side
const DirectBinary = require('@adobe/aem-upload');
const upload = new DirectBinary.DirectBinaryUpload();
const options = new DirectBinary.DirectBinaryUploadOptions()
.withUrl(`${host}/api/assets${assetPath}`)
.withUploadFiles(uploadFiles)
.withHttpOptions({ headers: { Authorization: `Bearer ${token}` } });
await upload.uploadFiles(options);
// Look up by repository path — replaces getAssetForBinary
Resource resource = resolver.getResource("/content/dam/my-site/report.pdf");
Asset asset = resource != null ? resource.adaptTo(Asset.class) : null;
The legacy "binary path" concept (a filesystem path under the AEM install) does not exist on AEMaaCS. Anywhere your legacy code passed a binary path, replace with the asset's repository path (/content/dam/...).
C2 — Decide whether `createAss
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: adobe
- Source: adobe/skills
- License: Apache-2.0
- Homepage: https://www.adobe.com/ai/overview.html
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.