Effective-Access Export
The effective-access export lets you download a complete, point-in-time snapshot of which principals can perform which actions on which resources for a given identity-provider connection. The export is produced automatically on every sync for opted-in AWS connections.
Enabling the export
Enable the export per-connection via the PATCH endpoint. Only AWS Account and AWS Organization connections are supported in v1.
PATCH /nhi/connections/{connection_id}
SlashID-OrgID: <your-org-id>
Content-Type: application/json
{
"source": "aws_account",
"access_export_enabled": true
}
The flag is persisted in the connection record. On the next successful sync the scanner writes the export object to GCS, and all subsequent syncs update it.
Retrieving the download URL
GET /nhi/connections/{connection_id}/access-export/download-url
SlashID-OrgID: <your-org-id>
A successful response (200 OK) contains:
{
"result": {
"url": "https://storage.googleapis.com/...",
"expires_at": "2024-01-15T10:30:00Z"
}
}
| Field | Description |
|---|---|
url | V4 pre-signed GCS URL, valid for 15 minutes. Download immediately and do not cache. |
expires_at | UTC timestamp after which the URL is no longer accepted by GCS. |
A 404 response means no export has been produced yet for this connection. Enable the flag and wait for the next sync to complete.
Decoding the export
The download is a zstd-compressed stream of length-delimited protobuf records (using the protodelim framing convention).
The canonical schema is published at:
https://cdn.slashid.com/schemas/effective-access-export/v1.proto
Decoding outline (Go):
import (
"bufio"
"github.com/klauspost/compress/zstd"
"google.golang.org/protobuf/encoding/protodelim"
)
decoder, _ := zstd.NewReader(httpBody)
defer decoder.Close()
br := bufio.NewReader(decoder) // protodelim.UnmarshalFrom requires io.ByteReader
for {
var rec accessexport.Record // generated from v1.proto
if err := protodelim.UnmarshalFrom(br, &rec); err == io.EOF {
break
}
// handle rec
}
Record ordering
The stream begins with a single Header, followed by all entity records (Resource and Principal, in no guaranteed sub-order relative to each other), followed by all Relationship records.
Because every entity precedes every relationship, a consumer should first index every entity by its common.handle, then process the relationships and resolve each from/to reference against that index.
Record types
Shared entity fields (EntityCommon)
Both Resource and Principal embed an EntityCommon message under the common field. These two identifiers are easy to confuse, so note the distinction:
| Field | Description |
|---|---|
handle | A uint64 export-local reference. It is ephemeral — valid only within this one export — and is what Relationship.from/to point at. Index entities by common.handle to resolve relationships. Not a business key. |
entity_id | The stable, global SlashID identifier (a sha256 hex string). Use this to deduplicate or join the entity to records in your own store across exports. |
source_type | The provider/source, e.g. aws_account. |
type | The entity type, e.g. s3_bucket, aws_iam_user. |
identifier | Source-native identifier (e.g. an ARN). |
source_identifier | The containing scope (the AWS account / project ID). |
display_name | Best human-readable label. |
Resource
Represents an AWS resource. In v1 the export emits only S3 buckets (type = s3_bucket; check the Header's included_resource_types); the schema is generic and may carry additional resource types in future versions. Carries only the shared common (EntityCommon) fields above; for a resource, source_identifier is the AWS account ID that owns it.
Principal
Represents a human or non-human identity that has access (IAM user, role, federated identity, etc.). Carries the shared common (EntityCommon) fields plus:
| Field | Description |
|---|---|
email | Email address, resolved via the canonical identity. May be empty (e.g. for roles and service accounts). When empty, fall back to common.identifier or common.display_name. |
canonical_identity_id | Groups principals that resolve to one human; may be empty. |
is_external | true when the principal belongs to a different AWS account than the resource's owner. |
Relationship
Represents a resolved effective-access grant: one principal can perform a set of actions on one resource.
| Field | Description |
|---|---|
from | A NodeRef pointing at the principal. |
to | A NodeRef pointing at the resource. |
type | A RelationshipType; in v1 always RELATIONSHIP_TYPE_CAN_ACCESS. |
granted_via | A repeated list of RelationshipKind values describing how the access was derived. See below. |
actions | List of literal IAM action strings (e.g. s3:GetObject), already deny-subtracted. |
A NodeRef is { handle (uint64), kind (NodeKind) }. To resolve a relationship, take from.handle / to.handle and look up the entity whose common.handle matches the value (using the index you built from the entity records).
NodeRef.kind (NodeKind)
| Value | Meaning |
|---|---|
NODE_KIND_UNSPECIFIED | Default / absent. Treat identically to NODE_KIND_REAL — real entities omit kind to save bytes. Resolve the handle to a Resource/Principal record. |
NODE_KIND_REAL | A real entity. Resolve the handle to a Resource/Principal record. |
NODE_KIND_VIRTUAL | An opaque compression node with no entity record. Do not attempt to resolve it to an entity; treat it as an intermediate node to traverse. Virtual nodes never appear in the Resource/Principal records. |
granted_via values (RelationshipKind)
| Value | Meaning |
|---|---|
RELATIONSHIP_KIND_DIRECT_POLICY | Granted by the principal's own attached (identity-based) policy. |
RELATIONSHIP_KIND_GROUP_MEMBERSHIP | Access flows through a group the principal belongs to. |
RELATIONSHIP_KIND_ROLE | Access is obtained via a role / assume-role. |
RELATIONSHIP_KIND_RESOURCE_POLICY | Granted by a resource-based policy. |
Mapping to your permission taxonomy
The actions list contains raw AWS IAM action strings. To map them to your own permission levels, enumerate the actions and categorize them — for example, treating s3:GetObject as read-only and s3:DeleteObject as destructive.
v1 fidelity limitations
The export is an over-approximation: it reflects what AWS IAM policies permit in isolation, without accounting for the full enforcement context.
- Service Control Policies (SCPs) and Organization Policies are evaluated as restrictions only — they can reduce the effective action set but cannot grant access by themselves. Cross-account paths that require both a trust policy and an SCP are modeled conservatively.
- Permission boundaries are not modeled. A principal that has a permission boundary applied may appear to have more access in the export than it does at runtime.
- Deny subtraction is literal-token only. An explicit
Denyons3:PutObjectwill remove that action, but it will not shrink an allowed wildcard such ass3:*. Wildcard action expansion is future work. emailmay be empty for service accounts and roles. Always fall back toidentifierordisplay_namebefore rendering.relationship_countin the header may be 0. Do not rely on it to pre-allocate; stream the records instead.- The export reflects the state at the time of the most recent successful sync, not real-time IAM state.